package com.google.maps.android.geojson; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.gms.maps.model.Polygon; import com.google.android.gms.maps.model.PolygonOptions; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.PolylineOptions; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Observable; import java.util.Observer; import java.util.Set; /** * Renders GeoJsonFeature objects onto the GoogleMap as Marker, Polyline and Polygon objects. Also * removes GeoJsonFeature objects and redraws features when updated. */ /* package */ class GeoJsonRenderer implements Observer { private final static int POLYGON_OUTER_COORDINATE_INDEX = 0; private final static int POLYGON_INNER_COORDINATE_INDEX = 1; private final static Object FEATURE_NOT_ON_MAP = null; private final BiMultiMap<GeoJsonFeature> mFeatures = new BiMultiMap<>(); private final GeoJsonPointStyle mDefaultPointStyle; private final GeoJsonLineStringStyle mDefaultLineStringStyle; private final GeoJsonPolygonStyle mDefaultPolygonStyle; private boolean mLayerOnMap; private GoogleMap mMap; /** * Creates a new GeoJsonRender object * * @param map map to place GeoJsonFeature objects on */ /* package */ GeoJsonRenderer(GoogleMap map, HashMap<GeoJsonFeature, Object> features) { mMap = map; mFeatures.putAll(features); mLayerOnMap = false; mDefaultPointStyle = new GeoJsonPointStyle(); mDefaultLineStringStyle = new GeoJsonLineStringStyle(); mDefaultPolygonStyle = new GeoJsonPolygonStyle(); // Add default styles to features for (GeoJsonFeature feature : getFeatures()) { setFeatureDefaultStyles(feature); } } /** * Given a Marker, Polyline, Polygon or an array of these and removes it from the map * * @param mapObject map object or array of map objects to remove from the map */ private static void removeFromMap(Object mapObject) { if (mapObject instanceof Marker) { ((Marker) mapObject).remove(); } else if (mapObject instanceof Polyline) { ((Polyline) mapObject).remove(); } else if (mapObject instanceof Polygon) { ((Polygon) mapObject).remove(); } else if (mapObject instanceof ArrayList) { for (Object mapObjectElement : (ArrayList) mapObject) { removeFromMap(mapObjectElement); } } } /* package */ boolean isLayerOnMap() { return mLayerOnMap; } /** * Gets the GoogleMap that GeoJsonFeature objects are being placed on * * @return GoogleMap */ /* package */ GoogleMap getMap() { return mMap; } /** * Changes the map that GeoJsonFeature objects are being drawn onto. Existing objects are * removed from the previous map and drawn onto the new map. * * @param map GoogleMap to place GeoJsonFeature objects on */ /* package */ void setMap(GoogleMap map) { for (GeoJsonFeature feature : getFeatures()) { redrawFeatureToMap(feature, map); } } /** * Adds all of the stored features in the layer onto the map if the layer is not already on the * map. */ /* package */ void addLayerToMap() { if (!mLayerOnMap) { mLayerOnMap = true; for (GeoJsonFeature feature : getFeatures()) { addFeature(feature); } } } /** * Gets a set containing GeoJsonFeatures * * @return set containing GeoJsonFeatures */ /* package */ Set<GeoJsonFeature> getFeatures() { return mFeatures.keySet(); } /** * Gets a GeoJsonFeature for the given map object, which is a Marker, Polyline or Polygon. * * @param mapObject Marker, Polyline or Polygon * @return GeoJsonFeature for the given map object */ /* package */ GeoJsonFeature getFeature(Object mapObject) { return mFeatures.getKey(mapObject); } /** * Checks for each style in the feature and adds a default style if none is applied * * @param feature feature to apply default styles to */ private void setFeatureDefaultStyles(GeoJsonFeature feature) { if (feature.getPointStyle() == null) { feature.setPointStyle(mDefaultPointStyle); } if (feature.getLineStringStyle() == null) { feature.setLineStringStyle(mDefaultLineStringStyle); } if (feature.getPolygonStyle() == null) { feature.setPolygonStyle(mDefaultPolygonStyle); } } /** * Adds a new GeoJsonFeature to the map if its geometry property is not null. * * @param feature feature to add to the map */ /* package */ void addFeature(GeoJsonFeature feature) { Object mapObject = FEATURE_NOT_ON_MAP; setFeatureDefaultStyles(feature); if (mLayerOnMap) { feature.addObserver(this); if (mFeatures.containsKey(feature)) { // Remove current map objects before adding new ones removeFromMap(mFeatures.get(feature)); } if (feature.hasGeometry()) { // Create new map object mapObject = addFeatureToMap(feature, feature.getGeometry()); } } mFeatures.put(feature, mapObject); } /** * Removes all GeoJsonFeature objects stored in the mFeatures hashmap from the map */ /* package */ void removeLayerFromMap() { if (mLayerOnMap) { for (GeoJsonFeature feature : mFeatures.keySet()) { removeFromMap(mFeatures.get(feature)); feature.deleteObserver(this); } mLayerOnMap = false; } } /** * Removes a GeoJsonFeature from the map if its geometry property is not null * * @param feature feature to remove from map */ /* package */ void removeFeature(GeoJsonFeature feature) { // Check if given feature is stored if (mFeatures.containsKey(feature)) { removeFromMap(mFeatures.remove(feature)); feature.deleteObserver(this); } } /** * Gets the default style used to render GeoJsonPoints * * @return default style used to render GeoJsonPoints */ /* package */ GeoJsonPointStyle getDefaultPointStyle() { return mDefaultPointStyle; } /** * Gets the default style used to render GeoJsonLineStrings * * @return default style used to render GeoJsonLineStrings */ /* package */ GeoJsonLineStringStyle getDefaultLineStringStyle() { return mDefaultLineStringStyle; } /** * Gets the default style used to render GeoJsonPolygons * * @return default style used to render GeoJsonPolygons */ /* package */ GeoJsonPolygonStyle getDefaultPolygonStyle() { return mDefaultPolygonStyle; } /** * Adds a new object onto the map using the GeoJsonGeometry for the coordinates and the * GeoJsonFeature for the styles. * * @param feature feature to get geometry style * @param geometry geometry to add to the map */ private Object addFeatureToMap(GeoJsonFeature feature, GeoJsonGeometry geometry) { String geometryType = geometry.getType(); if (geometryType.equals("Point")) { return addPointToMap(feature.getPointStyle(), (GeoJsonPoint) geometry); } else if (geometryType.equals("LineString")) { return addLineStringToMap(feature.getLineStringStyle(), (GeoJsonLineString) geometry); } else if (geometryType.equals("Polygon")) { return addPolygonToMap(feature.getPolygonStyle(), (GeoJsonPolygon) geometry); } else if (geometryType.equals("MultiPoint")) { return addMultiPointToMap(feature.getPointStyle(), (GeoJsonMultiPoint) geometry); } else if (geometryType.equals("MultiLineString")) { return addMultiLineStringToMap(feature.getLineStringStyle(), ((GeoJsonMultiLineString) geometry)); } else if (geometryType.equals("MultiPolygon")) { return addMultiPolygonToMap(feature.getPolygonStyle(), ((GeoJsonMultiPolygon) geometry)); } else if (geometryType.equals("GeometryCollection")) { return addGeometryCollectionToMap(feature, ((GeoJsonGeometryCollection) geometry).getGeometries()); } return null; } /** * Adds a GeoJsonPoint to the map as a Marker * * @param pointStyle contains relevant styling properties for the Marker * @param point contains coordinates for the Marker * @return Marker object created from the given GeoJsonPoint */ private Marker addPointToMap(GeoJsonPointStyle pointStyle, GeoJsonPoint point) { MarkerOptions markerOptions = pointStyle.toMarkerOptions(); markerOptions.position(point.getCoordinates()); return mMap.addMarker(markerOptions); } /** * Adds all GeoJsonPoint objects in GeoJsonMultiPoint to the map as multiple Markers * * @param pointStyle contains relevant styling properties for the Markers * @param multiPoint contains an array of GeoJsonPoints * @return array of Markers that have been added to the map */ private ArrayList<Marker> addMultiPointToMap(GeoJsonPointStyle pointStyle, GeoJsonMultiPoint multiPoint) { ArrayList<Marker> markers = new ArrayList<Marker>(); for (GeoJsonPoint geoJsonPoint : multiPoint.getPoints()) { markers.add(addPointToMap(pointStyle, geoJsonPoint)); } return markers; } /** * Adds a GeoJsonLineString to the map as a Polyline * * @param lineStringStyle contains relevant styling properties for the Polyline * @param lineString contains coordinates for the Polyline * @return Polyline object created from given GeoJsonLineString */ private Polyline addLineStringToMap(GeoJsonLineStringStyle lineStringStyle, GeoJsonLineString lineString) { PolylineOptions polylineOptions = lineStringStyle.toPolylineOptions(); // Add coordinates polylineOptions.addAll(lineString.getCoordinates()); Polyline addedPolyline = mMap.addPolyline(polylineOptions); addedPolyline.setClickable(true); return addedPolyline; } /** * Adds all GeoJsonLineString objects in the GeoJsonMultiLineString to the map as multiple * Polylines * * @param lineStringStyle contains relevant styling properties for the Polylines * @param multiLineString contains an array of GeoJsonLineStrings * @return array of Polylines that have been added to the map */ private ArrayList<Polyline> addMultiLineStringToMap(GeoJsonLineStringStyle lineStringStyle, GeoJsonMultiLineString multiLineString) { ArrayList<Polyline> polylines = new ArrayList<Polyline>(); for (GeoJsonLineString geoJsonLineString : multiLineString.getLineStrings()) { polylines.add(addLineStringToMap(lineStringStyle, geoJsonLineString)); } return polylines; } /** * Adds a GeoJsonPolygon to the map as a Polygon * * @param polygonStyle contains relevant styling properties for the Polygon * @param polygon contains coordinates for the Polygon * @return Polygon object created from given GeoJsonPolygon */ private Polygon addPolygonToMap(GeoJsonPolygonStyle polygonStyle, GeoJsonPolygon polygon) { PolygonOptions polygonOptions = polygonStyle.toPolygonOptions(); // First array of coordinates are the outline polygonOptions.addAll(polygon.getCoordinates().get(POLYGON_OUTER_COORDINATE_INDEX)); // Following arrays are holes for (int i = POLYGON_INNER_COORDINATE_INDEX; i < polygon.getCoordinates().size(); i++) { polygonOptions.addHole(polygon.getCoordinates().get(i)); } Polygon addedPolygon = mMap.addPolygon(polygonOptions); addedPolygon.setClickable(true); return addedPolygon; } /** * Adds all GeoJsonPolygon in the GeoJsonMultiPolygon to the map as multiple Polygons * * @param polygonStyle contains relevant styling properties for the Polygons * @param multiPolygon contains an array of GeoJsonPolygons * @return array of Polygons that have been added to the map */ private ArrayList<Polygon> addMultiPolygonToMap(GeoJsonPolygonStyle polygonStyle, GeoJsonMultiPolygon multiPolygon) { ArrayList<Polygon> polygons = new ArrayList<Polygon>(); for (GeoJsonPolygon geoJsonPolygon : multiPolygon.getPolygons()) { polygons.add(addPolygonToMap(polygonStyle, geoJsonPolygon)); } return polygons; } /** * Adds all GeoJsonGeometry objects stored in the GeoJsonGeometryCollection onto the map. * Supports recursive GeometryCollections. * * @param feature contains relevant styling properties for the GeoJsonGeometry inside * the GeoJsonGeometryCollection * @param geoJsonGeometries contains an array of GeoJsonGeometry objects * @return array of Marker, Polyline, Polygons that have been added to the map */ private ArrayList<Object> addGeometryCollectionToMap(GeoJsonFeature feature, List<GeoJsonGeometry> geoJsonGeometries) { ArrayList<Object> geometries = new ArrayList<Object>(); for (GeoJsonGeometry geometry : geoJsonGeometries) { geometries.add(addFeatureToMap(feature, geometry)); } return geometries; } /** * Redraws a given GeoJsonFeature onto the map. The map object is obtained from the mFeatures * hashmap and it is removed and added. * * @param feature feature to redraw onto the map */ private void redrawFeatureToMap(GeoJsonFeature feature) { redrawFeatureToMap(feature, mMap); } private void redrawFeatureToMap(GeoJsonFeature feature, GoogleMap map) { removeFromMap(mFeatures.get(feature)); mFeatures.put(feature, FEATURE_NOT_ON_MAP); mMap = map; if (map != null && feature.hasGeometry()) { mFeatures.put(feature, addFeatureToMap(feature, feature.getGeometry())); } } /** * Update is called if the developer sets a style or geometry in a GeoJsonFeature object * * @param observable GeoJsonFeature object * @param data null, no extra argument is passed through the notifyObservers method */ public void update(Observable observable, Object data) { if (observable instanceof GeoJsonFeature) { GeoJsonFeature feature = ((GeoJsonFeature) observable); boolean featureIsOnMap = mFeatures.get(feature) != FEATURE_NOT_ON_MAP; if (featureIsOnMap && feature.hasGeometry()) { // Checks if the feature has been added to the map and its geometry is not null // TODO: change this so that we don't add and remove redrawFeatureToMap(feature); } else if (featureIsOnMap && !feature.hasGeometry()) { // Checks if feature is on map and geometry is null removeFromMap(mFeatures.get(feature)); mFeatures.put(feature, FEATURE_NOT_ON_MAP); } else if (!featureIsOnMap && feature.hasGeometry()) { // Checks if the feature isn't on the map and geometry is not null addFeature(feature); } } } }