@@ -148,3 +148,282 @@ class GpxExporter {
148
148
}
149
149
}
150
150
}
151
+
152
+ class KmlExporter {
153
+ String IMAGES_SEPARATOR = ";" ;
154
+ late String dbName;
155
+ late bool useFiltered;
156
+
157
+ Future <void > exportDb (ProjectDb db, File outputFile) async {
158
+ dbName = HU .FileUtilities .nameFromFile (db.getPath (), false );
159
+
160
+ useFiltered = GpPreferences ().getBooleanSync (
161
+ SmashPreferencesKeys .KEY_GPS_USE_FILTER_GENERALLY , false );
162
+
163
+ var kmlString = "" ;
164
+ kmlString += "<?xml version=\" 1.0\" encoding=\" UTF-8\" ?>\n " ;
165
+ kmlString +=
166
+ "<kml xmlns=\" http://www.opengis.net/kml/2.2\" xmlns:gx=\" http://www.google.com/kml/ext/2.2\"\n " ;
167
+ kmlString +=
168
+ "xmlns:kml=\" http://www.opengis.net/kml/2.2\" xmlns:atom=\" http://www.w3.org/2005/Atom\" >\n " ;
169
+ kmlString += "<Document>\n " ;
170
+ kmlString += "<name>" ;
171
+ kmlString += dbName;
172
+ kmlString += "</name>\n " ;
173
+ kmlString += getMarker ("red-pushpin" ,
174
+ "http://maps.google.com/mapfiles/kml/pushpin/red-pushpin.png" , 20 , 2 );
175
+ kmlString += getMarker ("yellow-pushpin" ,
176
+ "http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png" , 20 , 2 );
177
+ kmlString += getMarker ("bookmark-icon" ,
178
+ "http://maps.google.com/mapfiles/kml/pal4/icon39.png" , 16 , 16 );
179
+ kmlString += getMarker ("camera-icon" ,
180
+ "http://maps.google.com/mapfiles/kml/pal4/icon38.png" , 16 , 16 );
181
+ kmlString += getMarker ("info-icon" ,
182
+ "http://maps.google.com/mapfiles/kml/pal3/icon35.png" , 16 , 16 );
183
+
184
+ var notes = db.getNotes ();
185
+ for (Note note in notes) {
186
+ kmlString += noteToKmlString (db, note);
187
+ }
188
+ var logs = db.getLogs ();
189
+ for (Log log in logs) {
190
+ kmlString += logToKmlString (db, log);
191
+ }
192
+ var simpleImages = db.getImages (onlySimple: true );
193
+ for (DbImage image in simpleImages) {
194
+ kmlString += simpleImageToKmlString (db, image);
195
+ }
196
+ kmlString += "</Document>\n " ;
197
+ kmlString += "</kml>\n " ;
198
+
199
+ final archive = Archive ();
200
+ archive.addFile (ArchiveFile .string ("kml.kml" , kmlString));
201
+
202
+ var images = db.getImages (onlySimple: false );
203
+ for (var image in images) {
204
+ var imgBytes = db.getImageDataBytes (image.imageDataId! );
205
+ archive.addFile (
206
+ ArchiveFile .noCompress (image.text, imgBytes! .length, imgBytes));
207
+ }
208
+ var zipData = ZipEncoder ().encode (archive, modified: DateTime .now ())! ;
209
+ await outputFile.writeAsBytes (zipData);
210
+ }
211
+
212
+ String logToKmlString (ProjectDb db, Log log) {
213
+ String name = makeXmlSafe (log.text);
214
+ var sB = "" ;
215
+ sB += "<Placemark>\n " ;
216
+ sB += "<name>${name }</name>\n " ;
217
+ sB += "<visibility>1</visibility>\n " ;
218
+ sB += "<LineString>\n " ;
219
+ sB += "<tessellate>1</tessellate>\n " ;
220
+ sB += "<coordinates>\n " ;
221
+
222
+ List <LogDataPoint > logDataPoints = db.getLogDataPointsById (log.id! );
223
+ for (LogDataPoint point in logDataPoints) {
224
+ sB += "${point .lon },${point .lat },1 \n " ;
225
+ }
226
+ sB += "</coordinates>\n " ;
227
+ sB += "</LineString>\n " ;
228
+ sB += "<Style>\n " ;
229
+ sB += "<LineStyle>\n " ;
230
+ // int parsedColor = ColorUtilities.toColor(color);
231
+ LogProperty ? logProperties = db.getLogProperties (log.id! );
232
+ String hexColor = logProperties! .color ?? "#FF0000" ;
233
+ sB += "<color>${hexColor }</color>\n " ;
234
+ sB += "<width>${logProperties .width }</width>\n " ;
235
+ sB += "</LineStyle>\n " ;
236
+ sB += "</Style>\n " ;
237
+ sB += "</Placemark>\n " ;
238
+
239
+ return sB.toString ();
240
+ }
241
+
242
+ String noteToKmlString (ProjectDb db, Note note) {
243
+ var images = [];
244
+ String name = makeXmlSafe (note.text);
245
+ if (note.hasForm ()) {
246
+ name = FormUtilities .getFormItemLabel (note.form! , name);
247
+ }
248
+ var sB = "<Placemark>\n " ;
249
+ sB += "<styleUrl>#red-pushpin</styleUrl>\n " ;
250
+ // sB += "<styleUrl>#info-icon</styleUrl>\n";
251
+ sB += "<name>${name }</name>\n " ;
252
+ sB += "<description>\n " ;
253
+
254
+ if (note.hasForm ()) {
255
+ sB += "<![CDATA[\n " ;
256
+
257
+ Map <String , dynamic > sectionObject = jsonDecode (note.form! );
258
+ if (sectionObject.containsKey (ATTR_SECTIONNAME )) {
259
+ String sectionName = sectionObject[ATTR_SECTIONNAME ];
260
+ sB += "<h1>${sectionName }</h1>\n " ;
261
+ }
262
+
263
+ List <String > formsNames = TagsManager .getFormNames4Section (sectionObject);
264
+ for (String formName in formsNames) {
265
+ sB += "<h2>${formName }</h2>\n " ;
266
+
267
+ sB +=
268
+ "<table style=\" text-align: left; width: 100%;\" border=\" 1\" cellpadding=\" 5\" cellspacing=\" 2\" >" ;
269
+ sB += "<tbody>" ;
270
+
271
+ Map <String , dynamic >? form4Name =
272
+ TagsManager .getForm4Name (formName, sectionObject);
273
+ List <Map <String , dynamic >> formItems =
274
+ TagsManager .getFormItems (form4Name);
275
+ for (int i = 0 ; i < formItems.length; i++ ) {
276
+ Map <String , dynamic > formItem = formItems[i];
277
+ if (! formItem.containsKey (TAG_KEY )) {
278
+ continue ;
279
+ }
280
+
281
+ String type = formItem[TAG_TYPE ];
282
+ String key = formItem[TAG_KEY ];
283
+ String value = formItem[TAG_VALUE ];
284
+
285
+ String label = key;
286
+ if (formItem.containsKey (TAG_LABEL )) {
287
+ label = formItem[TAG_LABEL ];
288
+ }
289
+
290
+ if (type == TYPE_PICTURES ) {
291
+ if (value.trim ().length == 0 ) {
292
+ continue ;
293
+ }
294
+ var imageIdsSplit = value.split (IMAGES_SEPARATOR );
295
+ for (String imageId in imageIdsSplit) {
296
+ DbImage image = db.getImageById (int .parse (imageId));
297
+ String imgName = image.text;
298
+ sB += "<tr>" ;
299
+ sB +=
300
+ "<td colspan=\" 2\" style=\" text-align: left; vertical-align: top; width: 100%;\" >" ;
301
+ sB += "<img src=\" ${imgName }\" width=\" 300\" >" ;
302
+ sB += "</td>" ;
303
+ sB += "</tr>" ;
304
+
305
+ images.add (imageId);
306
+ }
307
+ } else if (type == TYPE_MAP ) {
308
+ if (value.trim ().length == 0 ) {
309
+ continue ;
310
+ }
311
+ sB += "<tr>" ;
312
+ String imageId = value.trim ();
313
+ DbImage image = db.getImageById (int .parse (imageId));
314
+ String imgName = image.text;
315
+ sB +=
316
+ "<td colspan=\" 2\" style=\" text-align: left; vertical-align: top; width: 100%;\" >" ;
317
+ sB += "<img src=\" ${imgName }\" width=\" 300\" >" ;
318
+ sB += "</td>" ;
319
+ sB += "</tr>" ;
320
+ images.add (imageId);
321
+ } else if (type == TYPE_SKETCH ) {
322
+ if (value.trim ().length == 0 ) {
323
+ continue ;
324
+ }
325
+ var imageIdsSplit = value.split (IMAGES_SEPARATOR );
326
+ for (String imageId in imageIdsSplit) {
327
+ DbImage image = db.getImageById (int .parse (imageId));
328
+ String imgName = image.text;
329
+ sB += "<tr>" ;
330
+ sB +=
331
+ "<td colspan=\" 2\" style=\" text-align: left; vertical-align: top; width: 100%;\" >" ;
332
+ sB += "<img src=\" ${imgName }\" width=\" 300\" >" ;
333
+ sB += "</td>" ;
334
+ sB += "</tr>" ;
335
+
336
+ images.add (imageId);
337
+ }
338
+ } else {
339
+ sB += "<tr>" ;
340
+ sB +=
341
+ "<td style=\" text-align: left; vertical-align: top; width: 50%;\" >" ;
342
+ sB += label;
343
+ sB += "</td>" ;
344
+ sB +=
345
+ "<td style=\" text-align: left; vertical-align: top; width: 50%;\" >" ;
346
+ sB += value;
347
+ sB += "</td>" ;
348
+ sB += "</tr>" ;
349
+ }
350
+ }
351
+ sB += "</tbody>" ;
352
+ sB += "</table>" ;
353
+ }
354
+ sB += "]]>\n " ;
355
+ } else {
356
+ String description = makeXmlSafe (note.description);
357
+ sB += description;
358
+ sB += "\n " ;
359
+ sB += TimeUtilities .ISO8601_TS_FORMATTER_MILLIS
360
+ .format (DateTime .fromMillisecondsSinceEpoch (note.timeStamp));
361
+ }
362
+
363
+ sB += "</description>\n " ;
364
+ sB += "<gx:balloonVisibility>1</gx:balloonVisibility>\n " ;
365
+ sB += "<Point>\n " ;
366
+ sB += "<coordinates>" ;
367
+ sB += note.lon.toString ();
368
+ sB += "," ;
369
+ sB += note.lat.toString ();
370
+ sB += ",0</coordinates>\n " ;
371
+ sB += "</Point>\n " ;
372
+ sB += "</Placemark>\n " ;
373
+
374
+ return sB.toString ();
375
+ }
376
+
377
+ String simpleImageToKmlString (ProjectDb db, DbImage image) {
378
+ String name = makeXmlSafe (image.text);
379
+ var kml = """
380
+ <Placemark>
381
+ <styleUrl>#camera-icon</styleUrl>
382
+ <name>${name }</name>
383
+ <description>
384
+ <![CDATA[
385
+ <h1>${name }</h1>
386
+ <table style="text-align: left; width: 100%;" border="1" cellpadding="5" cellspacing="2">
387
+ <tbody>
388
+ <tr>
389
+ <td colspan="2" style="text-align: left; vertical-align: top; width: 100%;">
390
+ <img src="${name }" width="300">
391
+ </td>
392
+ </tr>
393
+ </tbody>
394
+ </table>
395
+ ]]>
396
+ </description>
397
+ <gx:balloonVisibility>1</gx:balloonVisibility>
398
+ <Point>
399
+ <coordinates>${image .lon .toString ()},${image .lat .toString ()},0</coordinates>
400
+ </Point>
401
+ </Placemark>
402
+ """ ;
403
+
404
+ return kml;
405
+ }
406
+
407
+ String makeXmlSafe (String ? string) {
408
+ if (string == null ) return "" ;
409
+ string = string.replaceAll ("&" , "&" );
410
+ return string;
411
+ }
412
+
413
+ String getMarker (String alias, String url, int x, int y) {
414
+ var sb = """
415
+ <Style id="${alias }">
416
+ <IconStyle>
417
+ <scale>1.1</scale>
418
+ <Icon>
419
+ <href>${url }</href>
420
+ </Icon>
421
+ <hotSpot x="${x .toString ()}" y="${y .toString ()}" xunits="pixels" yunits="pixels" />
422
+ </IconStyle>
423
+ <ListStyle>
424
+ </ListStyle>
425
+ </Style>
426
+ """ ;
427
+ return sb;
428
+ }
429
+ }
0 commit comments