AI2 Component  (Version nb184)
GeoJSONUtil.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2016-2017 MIT, All rights reserved
3 // Released under the Apache License, Version 2.0
4 // http://www.apache.org/licenses/LICENSE-2.0
5 
6 package com.google.appinventor.components.runtime.util;
7 
8 import android.text.TextUtils;
9 import android.util.Log;
21 import com.google.common.annotations.VisibleForTesting;
22 import gnu.lists.FString;
23 import gnu.lists.LList;
24 import gnu.lists.Pair;
25 import org.json.JSONArray;
26 import org.json.JSONException;
27 import org.json.JSONObject;
28 import org.osmdroid.util.GeoPoint;
29 
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 import java.io.PrintStream;
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Map;
38 
40 
46 public final class GeoJSONUtil {
47  private static final java.util.Map<String, Integer> colors;
48  private static final int ERROR_CODE_MALFORMED_GEOJSON = -3;
49  private static final String ERROR_MALFORMED_GEOJSON = "Malformed GeoJSON response. Expected FeatureCollection as root element.";
50  private static final String ERROR_UNKNOWN_TYPE = "Unrecognized/invalid type in JSON object";
51  private static final String GEOJSON_COORDINATES = "coordinates";
52  private static final String GEOJSON_FEATURE = "Feature";
53  private static final String GEOJSON_FEATURECOLLECTION = "FeatureCollection";
54  private static final String GEOJSON_FEATURES = "features";
55  private static final String GEOJSON_GEOMETRY = "geometry";
56  private static final String GEOJSON_GEOMETRYCOLLECTION = "GeometryCollection";
57  private static final String GEOJSON_PROPERTIES = "properties";
58  private static final String GEOJSON_TYPE = "type";
59  private static final String PROPERTY_ANCHOR_HORIZONTAL = "anchorHorizontal";
60  private static final String PROPERTY_ANCHOR_VERTICAL = "anchorVertical";
61  private static final String PROPERTY_DESCRIPTION = "description";
62  private static final String PROPERTY_DRAGGABLE = "draggable";
63  private static final String PROPERTY_FILL = "fill";
64  private static final String PROPERTY_FILL_OPACITY = "fill-opacity";
65  private static final String PROPERTY_HEIGHT = "height";
66  private static final String PROPERTY_IMAGE = "image";
67  private static final String PROPERTY_INFOBOX = "infobox";
68  private static final String PROPERTY_STROKE = "stroke";
69  private static final String PROPERTY_STROKE_OPACITY = "stroke-opacity";
70  private static final String PROPERTY_STROKE_WIDTH = "stroke-width";
71  private static final String PROPERTY_TITLE = "title";
72  private static final String PROPERTY_WIDTH = "width";
73  private static final String PROPERTY_VISIBLE = "visible";
74  private static final int KEY = 1;
75  private static final int VALUE = 2;
76  // Indexes in YailList of Lat, Long in GeoJSON, which are reversed from how they are usually written
77  private static final int LATITUDE = 2;
78  private static final int LONGITUDE = 1;
79  private static final Map<String, PropertyApplication> SUPPORTED_PROPERTIES;
80 
81  private interface PropertyApplication {
82  void apply(MapFeature feature, Object value);
83  }
84 
85  static {
86  colors = new HashMap<String, Integer>();
87  colors.put("black", COLOR_BLACK);
88  colors.put("blue", COLOR_BLUE);
89  colors.put("cyan", COLOR_CYAN);
90  colors.put("darkgray", COLOR_DKGRAY);
91  colors.put("gray", COLOR_GRAY);
92  colors.put("green", COLOR_GREEN);
93  colors.put("lightgray", COLOR_LTGRAY);
94  colors.put("magenta", COLOR_MAGENTA);
95  colors.put("orange", COLOR_ORANGE);
96  colors.put("pink", COLOR_PINK);
97  colors.put("red", COLOR_RED);
98  colors.put("white", COLOR_WHITE);
99  colors.put("yellow", COLOR_YELLOW);
100 
101  SUPPORTED_PROPERTIES = new HashMap<String, PropertyApplication>();
102  SUPPORTED_PROPERTIES.put(PROPERTY_ANCHOR_HORIZONTAL.toLowerCase(), new PropertyApplication() {
103  @Override
104  public void apply(MapFeature feature, Object value) {
105  if (feature instanceof MapMarker) {
106  ((MapMarker) feature).AnchorHorizontal(parseIntegerOrString(value));
107  }
108  }
109  });
110  SUPPORTED_PROPERTIES.put(PROPERTY_ANCHOR_VERTICAL.toLowerCase(), new PropertyApplication() {
111  @Override
112  public void apply(MapFeature feature, Object value) {
113  if (feature instanceof MapMarker) {
114  ((MapMarker) feature).AnchorHorizontal();
115  }
116  }
117  });
118  SUPPORTED_PROPERTIES.put(PROPERTY_DESCRIPTION, new PropertyApplication() {
119  @Override
120  public void apply(MapFeature feature, Object value) {
121  feature.Description(value.toString());
122  }
123  });
124  SUPPORTED_PROPERTIES.put(PROPERTY_DRAGGABLE, new PropertyApplication() {
125  @Override
126  public void apply(MapFeature feature, Object value) {
127  feature.Draggable(parseBooleanOrString(value));
128  }
129  });
130  SUPPORTED_PROPERTIES.put(PROPERTY_FILL, new PropertyApplication() {
131  @Override
132  public void apply(MapFeature feature, Object value) {
133  if (feature instanceof HasFill) {
134  ((HasFill) feature).FillColor(value instanceof Number ? ((Number) value).intValue() :
135  parseColor(value.toString()));
136  }
137  }
138  });
139  SUPPORTED_PROPERTIES.put(PROPERTY_FILL_OPACITY, new PropertyApplication() {
140  @Override
141  public void apply(MapFeature feature, Object value) {
142  if (feature instanceof HasFill) {
143  ((HasFill) feature).FillOpacity(parseFloatOrString(value));
144  }
145  }
146  });
147  SUPPORTED_PROPERTIES.put(PROPERTY_HEIGHT, new PropertyApplication() {
148  @Override
149  public void apply(MapFeature feature, Object value) {
150  if (feature instanceof MapMarker) {
151  ((MapMarker) feature).Height(parseIntegerOrString(value));
152  }
153  }
154  });
155  SUPPORTED_PROPERTIES.put(PROPERTY_IMAGE, new PropertyApplication() {
156  @Override
157  public void apply(MapFeature feature, Object value) {
158  if (feature instanceof MapMarker) {
159  ((MapMarker) feature).ImageAsset(value.toString());
160  }
161  }
162  });
163  SUPPORTED_PROPERTIES.put(PROPERTY_INFOBOX, new PropertyApplication() {
164  @Override
165  public void apply(MapFeature feature, Object value) {
166  feature.EnableInfobox(parseBooleanOrString(value));
167  }
168  });
169  SUPPORTED_PROPERTIES.put(PROPERTY_STROKE, new PropertyApplication() {
170  @Override
171  public void apply(MapFeature feature, Object value) {
172  if (feature instanceof HasStroke) {
173  ((HasStroke) feature).StrokeColor(value instanceof Number ? ((Number) value).intValue() :
174  parseColor(value.toString()));
175  }
176  }
177  });
178  SUPPORTED_PROPERTIES.put(PROPERTY_STROKE_OPACITY, new PropertyApplication() {
179  @Override
180  public void apply(MapFeature feature, Object value) {
181  if (feature instanceof HasStroke) {
182  ((HasStroke) feature).StrokeOpacity(parseFloatOrString(value));
183  }
184  }
185  });
186  SUPPORTED_PROPERTIES.put(PROPERTY_STROKE_WIDTH, new PropertyApplication() {
187  @Override
188  public void apply(MapFeature feature, Object value) {
189  if (feature instanceof HasStroke) {
190  ((HasStroke) feature).StrokeWidth(parseIntegerOrString(value));
191  }
192  }
193  });
194  SUPPORTED_PROPERTIES.put(PROPERTY_TITLE, new PropertyApplication() {
195  @Override
196  public void apply(MapFeature feature, Object value) {
197  feature.Title(value.toString());
198  }
199  });
200  SUPPORTED_PROPERTIES.put(PROPERTY_WIDTH, new PropertyApplication() {
201  @Override
202  public void apply(MapFeature feature, Object value) {
203  if (feature instanceof MapMarker) {
204  ((MapMarker) feature).Width(parseIntegerOrString(value));
205  }
206  }
207  });
208  SUPPORTED_PROPERTIES.put(PROPERTY_VISIBLE, new PropertyApplication() {
209  @Override
210  public void apply(MapFeature feature, Object value) {
211  feature.Visible(parseBooleanOrString(value));
212  }
213  });
214  }
215 
216  private GeoJSONUtil() {}
217 
218  @VisibleForTesting
219  static int parseColor(final String value) {
220  String lcValue = value.toLowerCase();
221  Integer result = colors.get(lcValue);
222  if (result != null) {
223  return result;
224  } else if (lcValue.startsWith("#")) {
225  return parseColorHex(lcValue.substring(1));
226  } else if (lcValue.startsWith("&h")) {
227  return parseColorHex(lcValue.substring(2));
228  } else {
229  return COLOR_RED;
230  }
231  }
232 
233  @VisibleForTesting
234  static int parseColorHex(final String value) {
235  int argb = 0;
236  if (value.length() == 3) {
237  // 4-bit RGB
238  argb = 0xFF000000;
239  int hex;
240  for (int i = 0; i < value.length(); i++) {
241  hex = charToHex(value.charAt(i));
242  argb |= ((hex << 4) | hex) << (2-i)*8;
243  }
244  } else if (value.length() == 6) {
245  // 8-bit RGB
246  argb = 0xFF000000;
247  int hex;
248  for (int i = 0; i < 3; i++) {
249  hex = charToHex(value.charAt(2*i)) << 4 | charToHex(value.charAt(2*i+1));
250  argb |= hex << (2-i)*8;
251  }
252  } else if (value.length() == 8) {
253  // 8-bit ARGB
254  int hex;
255  for (int i = 0; i < 4; i++) {
256  hex = charToHex(value.charAt(2*i)) << 4 | charToHex(value.charAt(2*i+1));
257  argb |= hex << (3-i)*8;
258  }
259  } else {
260  throw new IllegalArgumentException();
261  }
262  return argb;
263  }
264 
265  @VisibleForTesting
266  static int charToHex(char c) {
267  if ( '0' <= c && c <= '9' ) {
268  return c - '0';
269  } else if ( 'a' <= c && c <= 'f' ) {
270  return c - 'a' + 10;
271  } else if ( 'A' <= c && c <= 'F' ) {
272  return c - 'A' + 10;
273  } else {
274  throw new IllegalArgumentException("Invalid hex character. Expected [0-9A-Fa-f].");
275  }
276  }
277 
278  public static MapFactory.MapFeature processGeoJSONFeature(final String logTag,
279  final MapFactory.MapFeatureContainer container, final YailList descriptions) {
280  String type = null;
281  YailList geometry = null;
282  YailList properties = null;
283  for (Object o : (LList) descriptions.getCdr()) {
284  YailList keyvalue = (YailList)o;
285  String key = keyvalue.getString(0);
286  Object value = keyvalue.getObject(1);
287  if (GEOJSON_TYPE.equals(key)) {
288  type = (String) value;
289  } else if (GEOJSON_GEOMETRY.equals(key)) {
290  geometry = (YailList) value;
291  } else if (GEOJSON_PROPERTIES.equals(key)) {
292  properties = (YailList) value;
293  } else {
294  Log.w(logTag, String.format("Unsupported field \"%s\" in JSON format", key));
295  }
296  }
297  if (!GEOJSON_FEATURE.equals(type)) {
298  throw new IllegalArgumentException(String.format("Unknown type \"%s\"", type));
299  }
300  if (geometry == null) {
301  throw new IllegalArgumentException("No geometry defined for feature.");
302  }
303  MapFactory.MapFeature feature = processGeometry(logTag, container, geometry);
304  if (properties != null) {
305  processProperties(logTag, feature, properties);
306  }
307  return feature;
308  }
309 
310  private static MapFactory.MapFeature processGeometry(final String logTag,
311  final MapFactory.MapFeatureContainer container, final YailList geometry) {
312  String type = null;
313  YailList coordinates = null;
314  for (Object o : (LList) geometry.getCdr()) {
315  YailList keyvalue = (YailList)o;
316  String key = keyvalue.getString(0);
317  Object value = keyvalue.getObject(1);
318  if (GEOJSON_TYPE.equals(key)) {
319  type = (String) value;
320  } else if (GEOJSON_COORDINATES.equals(key)) {
321  coordinates = (YailList) value;
322  } else {
323  Log.w(logTag, String.format("Unsupported field \"%s\" in JSON format", key));
324  }
325  }
326  if (coordinates == null) {
327  throw new IllegalArgumentException("No coordinates found in GeoJSON Feature");
328  }
329  return processCoordinates(container, type, coordinates);
330  }
331 
332  private static MapFeature processCoordinates(final MapFeatureContainer container,
333  final String type, final YailList coordinates) {
334  if (MapFactory.MapFeatureType.TYPE_POINT.equals(type)) {
335  return markerFromGeoJSON(container, coordinates);
336  } else if (MapFactory.MapFeatureType.TYPE_LINESTRING.equals(type)) {
337  return lineStringFromGeoJSON(container, coordinates);
338  } else if (MapFactory.MapFeatureType.TYPE_POLYGON.equals(type)) {
339  return polygonFromGeoJSON(container, coordinates);
340  } else if (MapFeatureType.TYPE_MULTIPOLYGON.equals(type)) {
341  return multipolygonFromGeoJSON(container, coordinates);
342  }
343  throw new IllegalArgumentException();
344  }
345 
346  private static MapFactory.MapMarker markerFromGeoJSON(final MapFeatureContainer container,
347  final YailList coordinates) {
348  if (coordinates.length() != 3) { // One entry for list header and two for lat, long pair
349  throw new IllegalArgumentException("Invalid coordinate supplied in GeoJSON");
350  }
351  Marker marker = new Marker(container);
352  marker.Latitude(((Number) coordinates.get(LATITUDE)).doubleValue());
353  marker.Longitude(((Number) coordinates.get(LONGITUDE)).doubleValue());
354  return marker;
355  }
356 
357  private static MapLineString lineStringFromGeoJSON(final MapFeatureContainer container,
358  final YailList coordinates) {
359  if (coordinates.size() < 2) {
360  throw new IllegalArgumentException("Too few coordinates supplied in GeoJSON");
361  }
362  LineString lineString = new LineString(container);
363  lineString.Points(swapCoordinates(coordinates));
364  return lineString;
365  }
366 
367  private static MapPolygon polygonFromGeoJSON(final MapFeatureContainer container,
368  final YailList coordinates) {
369  Polygon polygon = new Polygon(container);
370  Iterator i = coordinates.iterator();
371  i.next();
372  polygon.Points(swapCoordinates((YailList) i.next()));
373  if (i.hasNext()) {
374  polygon.HolePoints(YailList.makeList(swapNestedCoordinates((LList) ((Pair)coordinates.getCdr()).getCdr())));
375  }
376  polygon.Initialize();
377  return polygon;
378  }
379 
380  private static MapPolygon multipolygonFromGeoJSON(final MapFeatureContainer container,
381  final YailList coordinates) {
382  Polygon polygon = new Polygon(container);
383  List<YailList> points = new ArrayList<YailList>();
384  List<YailList> holePoints = new ArrayList<YailList>();
385  Iterator i = coordinates.iterator();
386  i.next();
387  while (i.hasNext()) {
388  YailList list = (YailList) i.next();
389  points.add(swapCoordinates((YailList) list.get(1)));
390  holePoints.add(YailList.makeList(swapNestedCoordinates((LList) ((Pair) list.getCdr()).getCdr())));
391  }
392  polygon.Points(YailList.makeList(points));
393  polygon.HolePoints(YailList.makeList(holePoints));
394  polygon.Initialize();
395  return polygon;
396  }
397 
398  private static void processProperties(final String logTag, final MapFactory.MapFeature feature,
399  final YailList properties) {
400  for (Object o : properties) {
401  if (o instanceof YailList) {
402  YailList pair = (YailList) o;
403  String key = pair.get(KEY).toString();
404  PropertyApplication application = SUPPORTED_PROPERTIES.get(key.toLowerCase());
405  if (application != null) {
406  application.apply(feature, pair.get(VALUE));
407  } else {
408  Log.i(logTag, String.format("Ignoring GeoJSON property \"%s\"", key));
409  }
410  }
411  }
412  }
413 
414  @VisibleForTesting
415  static boolean parseBooleanOrString(Object value) {
416  if (value instanceof Boolean) {
417  return (Boolean) value;
418  } else if (value instanceof String) {
419  return !("false".equalsIgnoreCase((String) value) || ((String) value).length() == 0);
420  } else if (value instanceof FString) {
421  return parseBooleanOrString(value.toString());
422  } else {
423  throw new IllegalArgumentException();
424  }
425  }
426 
427  @VisibleForTesting
428  static int parseIntegerOrString(Object value) {
429  if (value instanceof Number) {
430  return ((Number) value).intValue();
431  } else if (value instanceof String) {
432  return Integer.parseInt((String) value);
433  } else if (value instanceof FString) {
434  return Integer.parseInt(value.toString());
435  } else {
436  throw new IllegalArgumentException();
437  }
438  }
439 
440  @VisibleForTesting
441  static float parseFloatOrString(Object value) {
442  if (value instanceof Number) {
443  return ((Number) value).floatValue();
444  } else if (value instanceof String) {
445  return Float.parseFloat((String) value);
446  } else if (value instanceof FString) {
447  return Float.parseFloat(value.toString());
448  } else {
449  throw new IllegalArgumentException();
450  }
451  }
452 
453  public static List<YailList> getGeoJSONFeatures(final String logTag, final String content) throws JSONException {
454  JSONObject parsedData = new JSONObject(stripBOM(content));
455  JSONArray features = parsedData.getJSONArray(GEOJSON_FEATURES);
456  List<YailList> yailFeatures = new ArrayList<YailList>();
457  for (int i = 0; i < features.length(); i++) {
458  yailFeatures.add(jsonObjectToYail(logTag, features.getJSONObject(i)));
459  }
460  return yailFeatures;
461  }
462 
463  public static String getGeoJSONType(final String content, final String geojsonType) throws JSONException {
464  JSONObject parsedData = new JSONObject(stripBOM(content));
465  String type = parsedData.optString(geojsonType);
466  return type;
467  }
468 
469  private static YailList jsonObjectToYail(final String logTag, final JSONObject object) throws JSONException {
470  List<YailList> pairs = new ArrayList<YailList>();
471  @SuppressWarnings("unchecked") // json only allows String keys
472  Iterator<String> j = object.keys();
473  while (j.hasNext()) {
474  String key = j.next();
475  Object value = object.get(key);
476  if (value instanceof Boolean ||
477  value instanceof Integer ||
478  value instanceof Long ||
479  value instanceof Double ||
480  value instanceof String) {
481  pairs.add(YailList.makeList(new Object[] { key, value }));
482  } else if (value instanceof JSONArray) {
483  pairs.add(YailList.makeList(new Object[] { key, jsonArrayToYail(logTag, (JSONArray) value)}));
484  } else if (value instanceof JSONObject) {
485  pairs.add(YailList.makeList(new Object[] { key, jsonObjectToYail(logTag, (JSONObject) value)}));
486  } else if (!JSONObject.NULL.equals(value)) {
487  Log.wtf(logTag, ERROR_UNKNOWN_TYPE + ": " + value.getClass());
488  throw new IllegalArgumentException(ERROR_UNKNOWN_TYPE);
489  }
490  }
491  return YailList.makeList(pairs);
492  }
493 
494  private static YailList jsonArrayToYail(final String logTag, final JSONArray array) throws JSONException {
495  List<Object> items = new ArrayList<Object>();
496  for (int i = 0; i < array.length(); i++) {
497  Object value = array.get(i);
498  if (value instanceof Boolean ||
499  value instanceof Integer ||
500  value instanceof Long ||
501  value instanceof Double ||
502  value instanceof String) {
503  items.add(value);
504  } else if (value instanceof JSONArray) {
505  items.add(jsonArrayToYail(logTag, (JSONArray) value));
506  } else if (value instanceof JSONObject) {
507  items.add(jsonObjectToYail(logTag, (JSONObject) value));
508  } else if (!JSONObject.NULL.equals(value)) {
509  Log.wtf(logTag, ERROR_UNKNOWN_TYPE + ": " + value.getClass());
510  throw new IllegalArgumentException(ERROR_UNKNOWN_TYPE);
511  }
512  }
513  return YailList.makeList(items);
514  }
515 
516  private static String stripBOM(String content) {
517  if (content.charAt(0) == '\uFEFF') {
518  return content.substring(1);
519  } else {
520  return content;
521  }
522  }
523 
524  private static final class FeatureWriter implements MapFactory.MapFeatureVisitor<Void> {
525 
526  private final PrintStream out;
527 
528  private FeatureWriter(PrintStream out) {
529  this.out = out;
530  }
531 
532  private void writeType(String type) {
533  out.print("\"type\":\"");
534  out.print(type);
535  out.print("\"");
536  }
537 
538  private void writeProperty(String property, Object value) {
539  try {
540  String result = JsonUtil.getJsonRepresentation(value);
541  out.print(",\"");
542  out.print(property);
543  out.print("\":");
544  out.print(result);
545  } catch(JSONException e) {
546  Log.w("GeoJSONUtil", "Unable to serialize the value of \"" + property + "\" as JSON", e);
547  }
548  }
549 
550  private void writeProperty(String property, String value) {
551  if (value == null || TextUtils.isEmpty(value)) {
552  // Suppress empty values
553  return;
554  }
555  writeProperty(property, (Object) value);
556  }
557 
558  private void writeColorProperty(String property, int color) {
559  out.print(",\"");
560  out.print(property);
561  out.print("\":\"&H");
562  String unpadded = Integer.toHexString(color);
563  for (int i = 8; i > unpadded.length(); i--) {
564  out.print("0");
565  }
566  out.print(unpadded);
567  out.print("\"");
568  }
569 
570  private void writePointGeometry(GeoPoint point) {
571  out.print("\"geometry\":{\"type\":\"Point\",\"coordinates\":[");
572  out.print(point.getLongitude());
573  out.print(",");
574  out.print(point.getLatitude());
575  if (hasAltitude(point)) {
576  out.print(",");
577  out.print(point.getAltitude());
578  }
579  out.print("]}");
580  }
581 
582  private void writePropertiesHeader(String runtimeType) {
583  out.print(",\"properties\":{\"$Type\":\"" + runtimeType + "\"");
584  }
585 
586  private void writeProperties(MapFeature feature) {
587  writeProperty(PROPERTY_DESCRIPTION, feature.Description());
588  writeProperty(PROPERTY_DRAGGABLE, feature.Draggable());
589  writeProperty(PROPERTY_INFOBOX, feature.EnableInfobox());
590  writeProperty(PROPERTY_TITLE, feature.Title());
591  writeProperty(PROPERTY_VISIBLE, feature.Visible());
592  }
593 
594  private void writeProperties(HasStroke feature) {
595  writeColorProperty(PROPERTY_STROKE, feature.StrokeColor());
596  writeProperty(PROPERTY_STROKE_OPACITY, feature.StrokeOpacity());
597  writeProperty(PROPERTY_STROKE_WIDTH, feature.StrokeWidth());
598  }
599 
600  private void writeProperties(HasFill feature) {
601  writeColorProperty(PROPERTY_FILL, feature.FillColor());
602  writeProperty(PROPERTY_FILL_OPACITY, feature.FillOpacity());
603  }
604 
605  private void writePoints(List<GeoPoint> points) {
606  boolean first = true;
607  for (GeoPoint p : points) {
608  if (!first) out.print(',');
609  out.print("[");
610  out.print(p.getLongitude());
611  out.print(",");
612  out.print(p.getLatitude());
613  if (hasAltitude(p)) {
614  out.print(",");
615  out.print(p.getAltitude());
616  }
617  out.print("]");
618  first = false;
619  }
620  }
621 
622  private void writeLineGeometry(MapLineString lineString) {
623  out.print("\"geometry\":{\"type\":\"LineString\",\"coordinates\":[");
624  writePoints(lineString.getPoints());
625  out.print("]}");
626  }
627 
628  private void writeMultipolygonGeometryNoHoles(MapPolygon polygon) {
629  out.print("\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[");
630  Iterator<List<GeoPoint>> pointIterator = polygon.getPoints().iterator();
631  Iterator<List<List<GeoPoint>>> holePointIterator = polygon.getHolePoints().iterator();
632  boolean first = true;
633  while (pointIterator.hasNext()) {
634  if (!first) out.print(",");
635  out.print("[");
636  writePoints(pointIterator.next());
637  if (holePointIterator.hasNext()) {
638  for (List<GeoPoint> holePoints : holePointIterator.next()) {
639  out.print(",");
640  writePoints(holePoints);
641  }
642  }
643  out.print("]");
644  first = false;
645  }
646  out.print("]}");
647  }
648 
649  private void writePolygonGeometryNoHoles(MapPolygon polygon) {
650  out.print("\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[");
651  writePoints(polygon.getPoints().get(0));
652  if (!polygon.getHolePoints().isEmpty()) {
653  for (List<GeoPoint> points : polygon.getHolePoints().get(0)) {
654  out.print(",");
655  writePoints(points);
656  }
657  }
658  out.print("]}");
659  }
660 
661  private void writePolygonGeometry(MapPolygon polygon) {
662  if (polygon.getPoints().size() > 1) {
663  writeMultipolygonGeometryNoHoles(polygon);
664  } else {
665  writePolygonGeometryNoHoles(polygon);
666  }
667  }
668 
669  @Override
670  public Void visit(MapFactory.MapMarker marker, Object... arguments) {
671  out.print("{");
672  writeType(GEOJSON_FEATURE);
673  out.print(',');
674  writePointGeometry(marker.getCentroid());
675  writePropertiesHeader(marker.getClass().getName());
676  writeProperties((MapFeature) marker);
677  writeProperties((HasStroke) marker);
678  writeProperties((HasFill) marker);
679  writeProperty(PROPERTY_ANCHOR_HORIZONTAL, marker.AnchorHorizontal());
680  writeProperty(PROPERTY_ANCHOR_VERTICAL, marker.AnchorVertical());
681  writeProperty(PROPERTY_HEIGHT, marker.Height());
682  writeProperty(PROPERTY_IMAGE, marker.ImageAsset());
683  writeProperty(PROPERTY_WIDTH, marker.Width());
684  out.print("}}");
685  return null;
686  }
687 
688  @Override
689  public Void visit(MapFactory.MapLineString lineString, Object... arguments) {
690  out.print("{");
691  writeType(GEOJSON_FEATURE);
692  out.print(',');
693  writeLineGeometry(lineString);
694  writePropertiesHeader(lineString.getClass().getName());
695  writeProperties((MapFeature) lineString);
696  writeProperties((HasStroke) lineString);
697  out.print("}}");
698  return null;
699  }
700 
701  @Override
702  public Void visit(MapFactory.MapPolygon polygon, Object... arguments) {
703  out.print("{");
704  writeType(GEOJSON_FEATURE);
705  out.print(',');
706  writePolygonGeometry(polygon);
707  writePropertiesHeader(polygon.getClass().getName());
708  writeProperties((MapFeature) polygon);
709  writeProperties((HasStroke) polygon);
710  writeProperties((HasFill) polygon);
711  out.print("}}");
712  return null;
713  }
714 
715  @Override
716  public Void visit(MapFactory.MapCircle circle, Object... arguments) {
717  out.print("{");
718  writeType(GEOJSON_FEATURE);
719  out.print(',');
720  writePointGeometry(circle.getCentroid());
721  writePropertiesHeader(circle.getClass().getName());
722  writeProperties((MapFeature) circle);
723  writeProperties((HasStroke) circle);
724  writeProperties((HasFill) circle);
725  out.print("}}");
726  return null;
727  }
728 
729  @Override
730  public Void visit(MapFactory.MapRectangle rectangle, Object... arguments) {
731  out.print("{");
732  writeType(GEOJSON_FEATURE);
733  out.print(",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[");
734  out.print("[" + rectangle.WestLongitude() + "," + rectangle.NorthLatitude() + "],");
735  out.print("[" + rectangle.WestLongitude() + "," + rectangle.SouthLatitude() + "],");
736  out.print("[" + rectangle.EastLongitude() + "," + rectangle.SouthLatitude() + "],");
737  out.print("[" + rectangle.EastLongitude() + "," + rectangle.NorthLatitude() + "],");
738  out.print("[" + rectangle.WestLongitude() + "," + rectangle.NorthLatitude() + "]]}");
739  writePropertiesHeader(rectangle.getClass().getName());
740  writeProperties((MapFeature) rectangle);
741  writeProperties((HasStroke) rectangle);
742  writeProperties((HasFill) rectangle);
743  writeProperty("NorthLatitude", rectangle.NorthLatitude());
744  writeProperty("WestLongitude", rectangle.WestLongitude());
745  writeProperty("SouthLatitude", rectangle.SouthLatitude());
746  writeProperty("EastLongitude", rectangle.EastLongitude());
747  out.print("}}");
748  return null;
749  }
750 
751  // This is here because of an interaction between OSMDroid's (bad) default that not having an
752  // altitude is the same as having 0 altitude and a rule in Sonar that doubles shouldn't be
753  // directly compared. In this case, we actually want to know that the double value is exactly 0.
754  // It would be better if OSMDroid used NaN for the altitude if it was undefined, but such is life.
755  private static boolean hasAltitude(GeoPoint point) {
756  return Double.compare(0.0, point.getAltitude()) != 0;
757  }
758  }
759 
760  public static void writeFeaturesAsGeoJSON(List<MapFactory.MapFeature> featuresToSave, String path) throws IOException {
761  PrintStream out = null;
762  try {
763  out = new PrintStream(new FileOutputStream(path));
764  FeatureWriter writer = new FeatureWriter(out);
765  out.print("{\"type\": \"FeatureCollection\", \"features\":[");
766  MapFeature feature;
767  Iterator<MapFeature> it = featuresToSave.iterator();
768  if (it.hasNext()) {
769  feature = it.next();
770  feature.accept(writer);
771  while (it.hasNext()) {
772  feature = it.next();
773  out.print(',');
774  feature.accept(writer);
775  }
776  }
777  out.print("]}");
778  } finally {
779  IOUtils.closeQuietly("GeoJSONUtil", out);
780  }
781  }
782 
789  public static YailList swapCoordinates(YailList coordinates) {
790  Iterator i = coordinates.iterator();
791  i.next();
792  while (i.hasNext()) {
793  YailList coordinate = (YailList) i.next();
794  Object temp = coordinate.get(1);
795  Pair p = (Pair) coordinate.getCdr();
796  p.setCar(coordinate.get(2));
797  p = (Pair) p.getCdr();
798  p.setCar(temp);
799  }
800  return coordinates;
801  }
802 
803  public static <E> List<List<E>> swapCoordinates2(List<List<E>> coordinates) {
804  for (List<E> point : coordinates) {
805  E temp = point.get(0);
806  point.set(0, point.get(1));
807  point.set(1, temp);
808  }
809  return coordinates;
810  }
811 
812  public static LList swapNestedCoordinates(LList coordinates) {
813  LList it = coordinates;
814  while (!it.isEmpty()) {
815  swapCoordinates((YailList) it.get(0));
816  it = (LList) ((Pair) it).getCdr();
817  }
818  return coordinates;
819  }
820 }
com.google.appinventor.components.runtime.util.IOUtils
Definition: IOUtils.java:13
com.google.appinventor.components.runtime.util.YailList
Definition: YailList.java:26
com.google.appinventor.components.runtime.util
-*- mode: java; c-basic-offset: 2; -*-
Definition: AccountChooser.java:7
com.google.appinventor.components.runtime.util.MapFactory
Definition: MapFactory.java:30
com.google.appinventor.components.runtime.util.MapFactory.HasFill
Definition: MapFactory.java:888
com.google.appinventor.components.runtime.util.MapFactory.MapFeature.Description
void Description(String description)
com.google.appinventor.components.runtime.util.GeoJSONUtil.swapCoordinates
static YailList swapCoordinates(YailList coordinates)
Definition: GeoJSONUtil.java:789
com.google.appinventor.components
com.google.appinventor.components.runtime.util.YailList.makeList
static YailList makeList(Object[] objects)
Definition: YailList.java:59
com.google.appinventor.components.runtime.util.MapFactory.MapLineString
Definition: MapFactory.java:1373
com.google.appinventor.components.runtime.util.GeoJSONUtil.swapCoordinates2
static< E > List< List< E > > swapCoordinates2(List< List< E >> coordinates)
Definition: GeoJSONUtil.java:803
com.google.appinventor.components.runtime.util.MapFactory.MapPolygon
Definition: MapFactory.java:1410
com.google.appinventor.components.runtime.util.MapFactory.MapFeature.EnableInfobox
void EnableInfobox(boolean enable)
com.google.appinventor.components.runtime.util.MapFactory.MapFeature.accept
< T > T accept(MapFeatureVisitor< T > visitor, Object... arguments)
com.google.appinventor.components.runtime.util.GeoJSONUtil.getGeoJSONType
static String getGeoJSONType(final String content, final String geojsonType)
Definition: GeoJSONUtil.java:463
com.google.appinventor.components.runtime.util.MapFactory.MapFeatureVisitor< Void >::visit
T visit(MapMarker marker, Object... arguments)
com.google.appinventor.components.runtime.Polygon
Definition: Polygon.java:55
com.google.appinventor.components.runtime.util.MapFactory.MapMarker
Definition: MapFactory.java:1205
com.google.appinventor.components.runtime.util.MapFactory.MapFeature
Definition: MapFactory.java:588
com.google.appinventor.components.runtime.util.MapFactory.MapFeature.Visible
boolean Visible()
com.google.appinventor.components.runtime.util.YailList.getString
String getString(int index)
Definition: YailList.java:193
com.google.appinventor.components.runtime.util.YailObject.iterator
Iterator< T > iterator()
com.google.appinventor.components.runtime.util.MapFactory.HasStroke
Definition: MapFactory.java:919
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.Component
Definition: Component.java:17
com.google.appinventor.components.runtime.Map< String, PropertyApplication >
com.google.appinventor.components.runtime.util.GeoJSONUtil.swapNestedCoordinates
static LList swapNestedCoordinates(LList coordinates)
Definition: GeoJSONUtil.java:812
com.google.appinventor.components.runtime.util.MapFactory.MapFeatureType
Definition: MapFactory.java:1483
com.google
com
com.google.appinventor.components.runtime.Marker
Definition: Marker.java:50
com.google.appinventor.components.runtime.LineString
Definition: LineString.java:49
com.google.appinventor.components.runtime.util.MapFactory.MapFeatureContainer
Definition: MapFactory.java:800
com.google.appinventor.components.runtime.util.MapFactory.MapFeature.Draggable
void Draggable(boolean draggable)
com.google.appinventor.components.runtime.util.IOUtils.closeQuietly
static void closeQuietly(String tag, Closeable closeable)
Definition: IOUtils.java:17
com.google.appinventor.components.runtime.util.GeoJSONUtil.writeFeaturesAsGeoJSON
static void writeFeaturesAsGeoJSON(List< MapFactory.MapFeature > featuresToSave, String path)
Definition: GeoJSONUtil.java:760
com.google.appinventor.components.runtime.util.YailList.getObject
Object getObject(int index)
Definition: YailList.java:200
com.google.appinventor.components.runtime.util.GeoJSONUtil.processGeoJSONFeature
static MapFactory.MapFeature processGeoJSONFeature(final String logTag, final MapFactory.MapFeatureContainer container, final YailList descriptions)
Definition: GeoJSONUtil.java:278
com.google.appinventor.components.runtime.util.GeoJSONUtil
Definition: GeoJSONUtil.java:46
com.google.appinventor.components.runtime.util.MapFactory.MapFeature.Title
void Title(String title)
com.google.appinventor
com.google.appinventor.components.runtime.util.GeoJSONUtil.getGeoJSONFeatures
static List< YailList > getGeoJSONFeatures(final String logTag, final String content)
Definition: GeoJSONUtil.java:453