AI2 Component  (Version nb184)
MapFeatureContainerBase.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright © 2017 Massachusetts Institute of Technology, 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;
7 
8 import android.app.Activity;
9 import android.util.Log;
20 import org.json.JSONArray;
21 import org.json.JSONException;
22 import org.json.JSONObject;
23 
24 import java.io.BufferedReader;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.net.HttpURLConnection;
28 import java.net.MalformedURLException;
29 import java.net.URL;
30 import java.net.URLConnection;
31 import java.util.ArrayList;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.ListIterator;
35 import java.util.concurrent.CopyOnWriteArrayList;
36 
40 
41 @SimpleObject
43  private static final String TAG = MapFeatureContainerBase.class.getSimpleName();
44 
45  private static final int ERROR_CODE_MALFORMED_URL = -1;
46  private static final int ERROR_CODE_IO_EXCEPTION = -2;
47  private static final int ERROR_CODE_MALFORMED_GEOJSON = -3;
48  private static final int ERROR_CODE_UNKNOWN_TYPE = -4;
49  private static final String ERROR_MALFORMED_URL = "The URL is malformed";
50  private static final String ERROR_IO_EXCEPTION = "Unable to download content from URL";
51  private static final String ERROR_MALFORMED_GEOJSON = "Malformed GeoJSON response. Expected FeatureCollection as root element.";
52  private static final String ERROR_UNKNOWN_TYPE = "Unrecognized/invalid type in JSON object";
53  private static final String GEOJSON_TYPE = "type";
54  private static final String GEOJSON_FEATURECOLLECTION = "FeatureCollection";
55  private static final String GEOJSON_GEOMETRYCOLLECTION = "GeometryCollection";
56  private static final String GEOJSON_FEATURES = "features";
57 
62  protected List<MapFeature> features = new CopyOnWriteArrayList<MapFeature>();
63 
64  private final MapFactory.MapFeatureVisitor<Void> featureAdder = new MapFactory.MapFeatureVisitor<Void>() {
65  @Override
66  public Void visit(MapFactory.MapMarker marker, Object... arguments) {
67  addFeature(marker);
68  return null;
69  }
70 
71  @Override
72  public Void visit(MapFactory.MapLineString lineString, Object... arguments) {
73  addFeature(lineString);
74  return null;
75  }
76 
77  @Override
78  public Void visit(MapFactory.MapPolygon polygon, Object... arguments) {
79  addFeature(polygon);
80  return null;
81  }
82 
83  @Override
84  public Void visit(MapFactory.MapCircle circle, Object... arguments) {
85  addFeature(circle);
86  return null;
87  }
88 
89  @Override
90  public Void visit(MapFactory.MapRectangle rectangle, Object... arguments) {
91  addFeature(rectangle);
92  return null;
93  }
94  };
95 
96  @SuppressWarnings("WeakerAccess")
98  super(container);
99  }
100 
107  public void Features(YailList features) {
108  for (MapFactory.MapFeature feature : this.features) {
109  feature.removeFromMap();
110  }
111  this.features.clear();
112  ListIterator<?> it = features.listIterator(1);
113  while (it.hasNext()) {
114  Object o = it.next();
115  if (o instanceof MapFactory.MapFeature) {
116  this.addFeature((MapFactory.MapFeature) o);
117  }
118  }
119  getMap().getView().invalidate();
120  }
121 
130  description = "The list of features placed on this %type%. This list also includes any " +
131  "features created by calls to FeatureFromDescription")
132  public YailList Features() {
133  return YailList.makeList(features);
134  }
135 
144  @SimpleEvent(description = "The user clicked on a map feature.")
145  public void FeatureClick(MapFactory.MapFeature feature) {
146  EventDispatcher.dispatchEvent(this, "FeatureClick", feature);
147  if (getMap() != this) {
148  getMap().FeatureClick(feature);
149  }
150  }
151 
160  @SimpleEvent(description = "The user long-pressed on a map feature.")
161  public void FeatureLongClick(MapFactory.MapFeature feature) {
162  EventDispatcher.dispatchEvent(this, "FeatureLongClick", feature);
163  if (getMap() != this) {
164  getMap().FeatureLongClick(feature);
165  }
166  }
167 
176  @SimpleEvent(description = "The user started dragging a map feature.")
177  public void FeatureStartDrag(MapFactory.MapFeature feature) {
178  EventDispatcher.dispatchEvent(this, "FeatureStartDrag", feature);
179  if (getMap() != this) {
180  getMap().FeatureStartDrag(feature);
181  }
182  }
183 
192  @SimpleEvent(description = "The user dragged a map feature.")
193  public void FeatureDrag(MapFactory.MapFeature feature) {
194  EventDispatcher.dispatchEvent(this, "FeatureDrag", feature);
195  if (getMap() != this) {
196  getMap().FeatureDrag(feature);
197  }
198  }
199 
208  @SimpleEvent(description = "The user stopped dragging a map feature.")
209  public void FeatureStopDrag(MapFactory.MapFeature feature) {
210  EventDispatcher.dispatchEvent(this, "FeatureStopDrag", feature);
211  if (getMap() != this) {
212  getMap().FeatureStopDrag(feature);
213  }
214  }
215 
225  @SimpleFunction(description = "<p>Load a feature collection in " +
226  "<a href=\"https://en.wikipedia.org/wiki/GeoJSON\">GeoJSON</a> format from the given " +
227  "url. On success, the event GotFeatures will be raised with the given url and a list of " +
228  "the features parsed from the GeoJSON as a list of (key, value) pairs. On failure, the " +
229  "LoadError event will be raised with any applicable HTTP response code and error " +
230  "message.</p>")
231  public void LoadFromURL(final String url) {
232  AsynchUtil.runAsynchronously(new Runnable() {
233  public void run() {
234  performGet(url);
235  }
236  });
237  }
238 
261  public Object FeatureFromDescription(YailList description) {
262  try {
263  return processGeoJSONFeature(TAG, this, description);
264  } catch(IllegalArgumentException e) {
265  $form().dispatchErrorOccurredEvent(this, "FeatureFromDescription",
266  ERROR_CODE_MALFORMED_GEOJSON, e.getMessage());
267  return e.getMessage();
268  }
269  }
270 
280  @SimpleEvent(description = "A GeoJSON document was successfully read from url. The features " +
281  "specified in the document are provided as a list in features.")
282  public void GotFeatures(String url, YailList features) {
283  if (!EventDispatcher.dispatchEvent(this, "GotFeatures", url, features)) {
284  // If the app inventor hasn't defined GotFeatures, we by default create the features for them
285  Iterator it = features.iterator();
286  it.next(); // skip *list* symbol
287  while (it.hasNext()) {
288  FeatureFromDescription((YailList) it.next());
289  }
290  }
291  }
292 
299  @SimpleEvent(description = "An error was encountered while processing a GeoJSON document at " +
300  "the given url. The responseCode parameter will contain an HTTP status code and the " +
301  "errorMessage parameter will contain a detailed error message.")
302  public void LoadError(String url, int responseCode, String errorMessage) {
303  if (!EventDispatcher.dispatchEvent(this, "LoadError", url, responseCode, errorMessage)) {
304  // If the app inventor hasn't defined LoadError, we by default report it via the Form's error
305  // handler.
306  if (url.startsWith("file:")) {
307  $form().dispatchErrorOccurredEvent(this, "LoadFromURL",
309  } else {
310  $form().dispatchErrorOccurredEvent(this, "LoadFromURL",
312  }
313  }
314  }
315 
316  @Override
317  public Activity $context() {
318  return container.$context();
319  }
320 
321  @Override
322  public Form $form() {
323  return container.$form();
324  }
325 
326  @Override
327  public void $add(AndroidViewComponent component) {
328  throw new UnsupportedOperationException("Map.$add() called");
329  }
330 
331  @Override
332  public void setChildWidth(AndroidViewComponent component, int width) {
333  throw new UnsupportedOperationException("Map.setChildWidth called");
334  }
335 
336  @Override
337  public void setChildHeight(AndroidViewComponent component, int height) {
338  throw new UnsupportedOperationException("Map.setChildHeight called");
339  }
340 
341  public void removeFeature(MapFactory.MapFeature feature) {
342  features.remove(feature);
343  getMap().removeFeature(feature);
344  }
345 
346  @Override
347  public Iterator<MapFeature> iterator() {
348  return features.iterator();
349  }
350 
351  void addFeature(MapFactory.MapMarker marker) {
352  features.add(marker);
353  getMap().addFeature(marker);
354  }
355 
356  void addFeature(MapFactory.MapLineString polyline) {
357  features.add(polyline);
358  getMap().addFeature(polyline);
359  }
360 
361  void addFeature(MapFactory.MapPolygon polygon) {
362  features.add(polygon);
363  getMap().addFeature(polygon);
364  }
365 
366  void addFeature(MapFactory.MapCircle circle) {
367  features.add(circle);
368  getMap().addFeature(circle);
369  }
370 
371  void addFeature(MapFactory.MapRectangle rectangle) {
372  features.add(rectangle);
373  getMap().addFeature(rectangle);
374  }
375 
376  @Override
377  public void addFeature(MapFactory.MapFeature feature) {
378  feature.accept(featureAdder);
379  }
380 
381  private void performGet(final String url) {
382  try {
383  String jsonContent = loadUrl(url);
384  if (jsonContent == null) {
385  return;
386  }
387  processGeoJSON(url, jsonContent);
388  } catch(Exception e) {
389  Log.e(TAG, "Exception retreiving GeoJSON", e);
390  $form().dispatchErrorOccurredEvent(this, "LoadFromURL", ERROR_CODE_UNKNOWN_TYPE,
391  e.toString());
392  }
393  }
394 
395  private String loadUrl(final String url) {
396  try {
397  URLConnection connection = new URL(url).openConnection();
398  connection.connect();
399  if (connection instanceof HttpURLConnection) {
400  HttpURLConnection conn = (HttpURLConnection) connection;
401  final int responseCode = conn.getResponseCode();
402  final String responseMessage = conn.getResponseMessage();
403  if (responseCode != 200) {
404  $form().runOnUiThread(new Runnable() {
405  public void run() {
406  MapFeatureContainerBase.this.LoadError(url, responseCode, responseMessage);
407  }
408  });
409  conn.disconnect();
410  return null;
411  }
412  }
413  BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(),
414  "UTF-8"));
415  StringBuilder content = new StringBuilder();
416  String line;
417  while ((line = reader.readLine()) != null) {
418  content.append(line);
419  content.append("\n");
420  }
421  reader.close();
422  return content.toString();
423  } catch(MalformedURLException e) {
424  $form().runOnUiThread(new Runnable() {
425  public void run() {
426  MapFeatureContainerBase.this.LoadError(url, ERROR_CODE_MALFORMED_URL,
427  ERROR_MALFORMED_URL);
428  }
429  });
430  } catch (IOException e) {
431  $form().runOnUiThread(new Runnable() {
432  public void run() {
433  MapFeatureContainerBase.this.LoadError(url, ERROR_CODE_IO_EXCEPTION,
434  ERROR_IO_EXCEPTION);
435  }
436  });
437  }
438  return null;
439  }
440 
441  @SuppressWarnings("WeakerAccess")
442  protected void processGeoJSON(final String url, final String content) throws JSONException {
443  String type = getGeoJSONType(content, GEOJSON_TYPE);
444  if (!GEOJSON_FEATURECOLLECTION.equals(type) && !GEOJSON_GEOMETRYCOLLECTION.equals(type)) {
445  $form().runOnUiThread(new Runnable() {
446  public void run() {
447  MapFeatureContainerBase.this.LoadError(url, ERROR_CODE_MALFORMED_GEOJSON,
448  ERROR_MALFORMED_GEOJSON);
449  }
450  });
451  return;
452  }
453  final List<YailList> yailFeatures = getGeoJSONFeatures(TAG, content);
454  $form().runOnUiThread(new Runnable() {
455  public void run() {
456  MapFeatureContainerBase.this.GotFeatures(url, YailList.makeList(yailFeatures));
457  }
458  });
459  }
460 }
com.google.appinventor.components.runtime.MapFeatureContainerBase.$form
Form $form()
Definition: MapFeatureContainerBase.java:322
com.google.appinventor.components.runtime.EventDispatcher
Definition: EventDispatcher.java:22
com.google.appinventor.components.runtime.MapFeatureContainerBase
Definition: MapFeatureContainerBase.java:42
com.google.appinventor.components.runtime.util.YailList
Definition: YailList.java:26
com.google.appinventor.components.runtime.MapFeatureContainerBase.features
List< MapFeature > features
Definition: MapFeatureContainerBase.java:62
com.google.appinventor.components.annotations.SimpleFunction
Definition: SimpleFunction.java:23
com.google.appinventor.components.runtime.MapFeatureContainerBase.setChildHeight
void setChildHeight(AndroidViewComponent component, int height)
Definition: MapFeatureContainerBase.java:337
com.google.appinventor.components.runtime.Map.removeFeature
void removeFeature(MapFeature feature)
Definition: Map.java:825
com.google.appinventor.components.runtime.util.ErrorMessages
Definition: ErrorMessages.java:17
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.MapFeatureContainerBase.FeatureLongClick
void FeatureLongClick(MapFactory.MapFeature feature)
Definition: MapFeatureContainerBase.java:161
com.google.appinventor.components.runtime.MapFeatureContainerBase.FeatureClick
void FeatureClick(MapFactory.MapFeature feature)
Definition: MapFeatureContainerBase.java:145
com.google.appinventor.components
com.google.appinventor.components.runtime.MapFeatureContainerBase.setChildWidth
void setChildWidth(AndroidViewComponent component, int width)
Definition: MapFeatureContainerBase.java:332
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.MapFeatureContainerBase.$context
Activity $context()
Definition: MapFeatureContainerBase.java:317
com.google.appinventor.components.runtime.MapFeatureContainerBase.FeatureStartDrag
void FeatureStartDrag(MapFactory.MapFeature feature)
Definition: MapFeatureContainerBase.java:177
com.google.appinventor.components.runtime.util.MapFactory.MapFeatureContainer.getMap
Map getMap()
com.google.appinventor.components.annotations.SimpleEvent
Definition: SimpleEvent.java:20
com.google.appinventor.components.runtime.MapFeatureContainerBase.MapFeatureContainerBase
MapFeatureContainerBase(ComponentContainer container)
Definition: MapFeatureContainerBase.java:97
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.MapFeatureContainerBase.LoadFromURL
void LoadFromURL(final String url)
Definition: MapFeatureContainerBase.java:231
com.google.appinventor.components.runtime.MapFeatureContainerBase.GotFeatures
void GotFeatures(String url, YailList features)
Definition: MapFeatureContainerBase.java:282
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.ErrorMessages.ERROR_CANNOT_READ_FILE
static final int ERROR_CANNOT_READ_FILE
Definition: ErrorMessages.java:177
com.google.appinventor.components.runtime.EventDispatcher.dispatchEvent
static boolean dispatchEvent(Component component, String eventName, Object...args)
Definition: EventDispatcher.java:188
com.google.appinventor.components.annotations.SimpleProperty
Definition: SimpleProperty.java:23
com.google.appinventor.components.runtime.MapFeatureContainerBase.FeatureDrag
void FeatureDrag(MapFactory.MapFeature feature)
Definition: MapFeatureContainerBase.java:193
com.google.appinventor.components.runtime.Map.getView
View getView()
Definition: Map.java:123
com.google.appinventor.components.runtime.util.AsynchUtil.runAsynchronously
static void runAsynchronously(final Runnable call)
Definition: AsynchUtil.java:23
com.google.appinventor.components.annotations.PropertyCategory
Definition: PropertyCategory.java:13
com.google.appinventor.components.runtime.ComponentContainer
Definition: ComponentContainer.java:16
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.MapFeatureContainerBase.LoadError
void LoadError(String url, int responseCode, String errorMessage)
Definition: MapFeatureContainerBase.java:302
com.google.appinventor.components.runtime.MapFeatureContainerBase.Features
void Features(YailList features)
Definition: MapFeatureContainerBase.java:107
com.google.appinventor.components.runtime.MapFeatureContainerBase.Features
YailList Features()
Definition: MapFeatureContainerBase.java:132
com.google.appinventor.components.runtime.MapFeatureContainerBase.$add
void $add(AndroidViewComponent component)
Definition: MapFeatureContainerBase.java:327
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_WEB_UNABLE_TO_GET
static final int ERROR_WEB_UNABLE_TO_GET
Definition: ErrorMessages.java:127
com.google.appinventor.components.runtime.Form.dispatchErrorOccurredEvent
void dispatchErrorOccurredEvent(final Component component, final String functionName, final int errorNumber, final Object... messageArgs)
Definition: Form.java:1011
com.google.appinventor.components.annotations.SimpleObject
Definition: SimpleObject.java:23
com.google.appinventor.components.runtime.AndroidViewComponent.container
final ComponentContainer container
Definition: AndroidViewComponent.java:29
com.google.appinventor.components.runtime.util.AsynchUtil
Definition: AsynchUtil.java:17
com.google.appinventor.components.runtime.MapFeatureContainerBase.FeatureStopDrag
void FeatureStopDrag(MapFactory.MapFeature feature)
Definition: MapFeatureContainerBase.java:209
com.google.appinventor.components.runtime.MapFeatureContainerBase.FeatureFromDescription
Object FeatureFromDescription(YailList description)
Definition: MapFeatureContainerBase.java:261
com.google
com
com.google.appinventor.components.runtime.MapFeatureContainerBase.iterator
Iterator< MapFeature > iterator()
Definition: MapFeatureContainerBase.java:347
com.google.appinventor.components.runtime.ComponentContainer.$form
Form $form()
com.google.appinventor.components.runtime.ComponentContainer.$context
Activity $context()
com.google.appinventor.components.runtime.util.MapFactory.MapFeatureContainer
Definition: MapFactory.java:800
com.google.appinventor.components.runtime.MapFeatureContainerBase.addFeature
void addFeature(MapFactory.MapFeature feature)
Definition: MapFeatureContainerBase.java:377
com.google.appinventor.components.runtime.AndroidViewComponent
Definition: AndroidViewComponent.java:27
com.google.appinventor.components.runtime.Form
Definition: Form.java:126
com.google.appinventor.components.runtime.util.MapFactory.MapFeatureVisitor
Definition: MapFactory.java:745
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.annotations.PropertyCategory.APPEARANCE
APPEARANCE
Definition: PropertyCategory.java:16
com.google.appinventor.components.runtime.MapFeatureContainerBase.processGeoJSON
void processGeoJSON(final String url, final String content)
Definition: MapFeatureContainerBase.java:442
com.google.appinventor.components.annotations
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
com.google.appinventor.components.runtime.MapFeatureContainerBase.removeFeature
void removeFeature(MapFactory.MapFeature feature)
Definition: MapFeatureContainerBase.java:341