1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 Julian Sherollari <jdotsh@gmail.com>
4 ** Copyright (C) 2019 The Qt Company Ltd.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the examples of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:BSD$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** BSD License Usage
19 ** Alternatively, you may use this file under the terms of the BSD license
20 ** as follows:
21 **
22 ** "Redistribution and use in source and binary forms, with or without
23 ** modification, are permitted provided that the following conditions are
24 ** met:
25 **   * Redistributions of source code must retain the above copyright
26 **     notice, this list of conditions and the following disclaimer.
27 **   * Redistributions in binary form must reproduce the above copyright
28 **     notice, this list of conditions and the following disclaimer in
29 **     the documentation and/or other materials provided with the
30 **     distribution.
31 **   * Neither the name of The Qt Company Ltd nor the names of its
32 **     contributors may be used to endorse or promote products derived
33 **     from this software without specific prior written permission.
34 **
35 **
36 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
37 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
38 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
39 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
40 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
43 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
45 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
46 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
47 **
48 ** $QT_END_LICENSE$
49 **
50 ****************************************************************************/
51 
52 #include <QApplication>
53 #include <QQmlApplicationEngine>
54 #include <QDebug>
55 #include <QFile>
56 #include <QJsonDocument>
57 #include <QVariantMap>
58 #include <QQmlContext>
59 #include <QtLocation/private/qgeojson_p.h>
60 #include <QGeoCircle>
61 #include <QGeoPath>
62 #include <QGeoPolygon>
63 #include <QtLocation/private/qdeclarativegeomapitemview_p.h>
64 #include <QtLocation/private/qdeclarativegeomapquickitem_p.h>
65 #include <QtLocation/private/qdeclarativecirclemapitem_p.h>
66 #include <QtLocation/private/qdeclarativepolylinemapitem_p.h>
67 #include <QtLocation/private/qdeclarativepolygonmapitem_p.h>
68 #include <QtLocation/private/qdeclarativerectanglemapitem_p.h>
69 #include <QJsonObject>
70 #include <QJsonArray>
71 #include <QFileInfo>
72 #include <QtCore/qobjectdefs.h>
73 #ifdef Q_OS_ANDROID
74 #include <QtAndroid>
75 #endif
76 
77 class extractor
78 {
79 public:
80     extractor();
81 
hasProperties(QQuickItem * item)82     static bool hasProperties(QQuickItem *item)
83     {
84         QVariant props = item->property("props");
85         return !props.isNull();
86     }
87 
isFeatureCollection(QQuickItem * item)88     static bool isFeatureCollection(QQuickItem *item)
89     {
90         QVariant geoJsonType = item->property("geojsonType");
91         return geoJsonType.toString() == QStringLiteral("FeatureCollection");
92     }
93 
isGeoJsonEntry(QQuickItem * item)94     static bool isGeoJsonEntry(QQuickItem *item)
95     {
96         QVariant geoJsonType = item->property("geojsonType");
97         return !geoJsonType.toString().isEmpty();
98     }
99 
toVariant(QDeclarativePolygonMapItem * mapPolygon)100     static QVariantMap toVariant(QDeclarativePolygonMapItem *mapPolygon)
101     {
102         QVariantMap ls;
103         ls["type"] = "Polygon";
104         ls["data"] = QVariant::fromValue(mapPolygon->geoShape());
105         if (hasProperties(mapPolygon))
106             ls["properties"] = mapPolygon->property("props").toMap();
107         return ls;
108     }
toVariant(QDeclarativePolylineMapItem * mapPolyline)109     static QVariantMap toVariant(QDeclarativePolylineMapItem *mapPolyline)
110     {
111         QVariantMap ls;
112         ls["type"] = "LineString";
113         ls["data"] = QVariant::fromValue(mapPolyline->geoShape());
114         if (hasProperties(mapPolyline))
115             ls["properties"] = mapPolyline->property("props").toMap();
116         return ls;
117     }
toVariant(QDeclarativeCircleMapItem * mapCircle)118     static QVariantMap toVariant(QDeclarativeCircleMapItem *mapCircle)
119     {
120         QVariantMap pt;
121         pt["type"] = "Point";
122         pt["data"] = QVariant::fromValue(mapCircle->geoShape());
123         if (hasProperties(mapCircle))
124             pt["properties"] = mapCircle->property("props").toMap();
125         return pt;
126     }
127 
toVariant(QDeclarativeGeoMapItemView * mapItemView)128     static QVariantMap toVariant(QDeclarativeGeoMapItemView *mapItemView)
129     {
130         // bool featureCollecton = isFeatureCollection(mapItemView);
131 
132         // If not a feature collection, this must be a geometry collection,
133         // or a multilinestring/multipoint/multipolygon.
134         // To disambiguate, one could check for heterogeneity.
135         // For simplicity, in this example, we expect the property "geojsonType" to be injected in the mapItemView
136         // by the delegate, and to be correct.
137 
138         QString nodeType = mapItemView->property("geojsonType").toString();
139         QVariantMap root;
140         if (!nodeType.isEmpty())  // Empty nodeType can happen only for the root MIV
141             root["type"] = nodeType;
142         if (hasProperties(mapItemView)) // Features are converted to regular types w properties.
143             root["properties"] = mapItemView->property("props").toMap();
144 
145         QVariantList features;
146         const QList<QQuickItem *> &quickChildren = mapItemView->childItems();
147         for (auto kid : quickChildren) {
148             QVariant entry;
149             if (QDeclarativeGeoMapItemView *miv = qobject_cast<QDeclarativeGeoMapItemView *>(kid)) {
150                 // Handle nested miv
151                 entry = toVariant(miv);
152             } else if (QDeclarativePolylineMapItem *polyline = qobject_cast<QDeclarativePolylineMapItem *>(kid)) {
153                 entry = toVariant(polyline);
154             } else if (QDeclarativePolygonMapItem *polygon = qobject_cast<QDeclarativePolygonMapItem *>(kid)) {
155                 entry = toVariant(polygon);
156             } else if (QDeclarativeCircleMapItem *circle = qobject_cast<QDeclarativeCircleMapItem *>(kid)) {
157                 entry = toVariant(circle); // If GeoJSON Point type is visualized in other ways, handle those types here instead.
158             }
159             features.append(entry);
160         }
161         if (nodeType.isEmpty()) // Dirty hack to handle (=skip) the first MIV used to process the fictitious list with 1 element
162             return features.first().toMap();
163         root["data"] = features;
164         return root;
165     }
166 };
167 
168 class GeoJsoner: public QObject
169 {
170     Q_OBJECT
171     Q_PROPERTY(QVariant model MEMBER m_importedGeoJson NOTIFY modelChanged)
172 
173 public:
GeoJsoner(QObject * parent=nullptr)174     GeoJsoner(QObject *parent = nullptr) : QObject(parent)
175     {
176 
177     }
178 
179 public slots:
180 
load(QUrl url)181     Q_INVOKABLE bool load(QUrl url)
182     {
183         // Reading GeoJSON file
184         QFile loadFile(url.toLocalFile());
185         if (!loadFile.open(QIODevice::ReadOnly)) {
186             qWarning() << "Error while opening the file: " << url;
187             qWarning() << loadFile.error() <<  loadFile.errorString();
188             return false;
189         }
190 
191         // Load the GeoJSON file using Qt's API
192         QJsonParseError err;
193         QJsonDocument loadDoc(QJsonDocument::fromJson(loadFile.readAll(), &err));
194         if (err.error) {
195              qWarning() << "Parsing while importing the JSON document:\n" << err.errorString();
196              return false;
197         }
198 
199         // Import geographic data to a QVariantList
200         QVariantList modelList = QGeoJson::importGeoJson(loadDoc);
201         m_importedGeoJson =  modelList;
202         emit modelChanged();
203         return true;
204     }
205 
206     // Used by the MapItemView Extractor to identify a Feature
toGeoJson(QDeclarativeGeoMapItemView * mapItemView)207     Q_INVOKABLE QVariantList toGeoJson(QDeclarativeGeoMapItemView *mapItemView)
208     {
209         QVariantList res;
210         QDeclarativeGeoMapItemView *root = mapItemView;
211         QVariantMap miv = extractor::toVariant(root);
212         if (!miv.isEmpty())
213             res.append(miv);
214         return res;
215     }
216 
dumpGeoJSON(QVariantList geoJson,QUrl url)217     Q_INVOKABLE void dumpGeoJSON(QVariantList geoJson, QUrl url)
218     {
219         QJsonDocument json = QGeoJson::exportGeoJson(geoJson);
220         QFile jsonFile(url.toLocalFile());
221         jsonFile.open(QIODevice::WriteOnly);
222         jsonFile.write(json.toJson());
223         jsonFile.close();
224     }
225 
writeDebug(QVariantList geoJson,QUrl url)226     Q_INVOKABLE void writeDebug(QVariantList geoJson, QUrl url)
227     {
228         QString prettyPrint = QGeoJson::toString(geoJson);
229         QFile debugFile(url.toLocalFile());
230         debugFile.open(QIODevice::WriteOnly);
231         debugFile.write(prettyPrint.toUtf8());
232         debugFile.close();
233     }
234 
print(QDeclarativeGeoMapItemView * view)235     Q_INVOKABLE void print(QDeclarativeGeoMapItemView *view)
236     {
237         QVariantList list;
238         list.append(extractor::toVariant(view));
239         QString prettyPrint =
240                 QGeoJson::toString(list);
241         qDebug().noquote() << prettyPrint;
242     }
243 
244 signals:
245     void modelChanged();
246 
247 public:
248     QVariant m_importedGeoJson;
249 };
250 
251 #include "main.moc"
252 
253 #ifdef Q_OS_ANDROID
254 // Request permissions because we're using QStandardPaths::writableLocation()
requestStoragePermissions()255 bool requestStoragePermissions() {
256     using namespace QtAndroid;
257 
258     QString permission = QStringLiteral("android.permission.WRITE_EXTERNAL_STORAGE");
259     const QHash<QString, PermissionResult> results = requestPermissionsSync(QStringList({permission}));
260     if (!results.contains(permission) || results[permission] == PermissionResult::Denied) {
261         qWarning() << "Couldn't get permission: " << permission;
262         return false;
263     }
264 
265     return true;
266 }
267 #endif
268 
main(int argc,char * argv[])269 int main(int argc, char *argv[])
270 {
271     QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
272     QApplication app(argc, argv);
273 #ifdef Q_OS_ANDROID
274     if (!requestStoragePermissions())
275         return -1;
276 #endif
277 
278     QQmlApplicationEngine engine;
279     QUrl absoluteFilePath = argc > 1 ?
280                     QUrl(QStringLiteral("file://") + QFileInfo(argv[1]).absoluteFilePath()) :
281                     QUrl();
282     engine.rootContext()->setContextProperty("dataPath", QUrl(QStringLiteral("file://")
283                                                               + qPrintable(QT_STRINGIFY(SRC_PATH))
284                                                               + QStringLiteral("/data")));
285     qmlRegisterType<GeoJsoner>("Qt.GeoJson", 1, 0, "GeoJsoner");
286     engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
287 
288     if (engine.rootObjects().isEmpty())
289         return -1;
290     if (!absoluteFilePath.isEmpty()) {
291         GeoJsoner *geoJsoner = engine.rootObjects().first()->findChild<GeoJsoner*>();
292         QMetaObject::invokeMethod(geoJsoner, "load", Qt::QueuedConnection, Q_ARG(QUrl, absoluteFilePath));
293     }
294 
295     return app.exec();
296 }
297