/* * Copyright (C) 2013 Maciej Górski * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package pl.mg6.android.maps.extensions.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import pl.mg6.android.maps.extensions.Circle; import pl.mg6.android.maps.extensions.ClusteringSettings; import pl.mg6.android.maps.extensions.GoogleMap; import pl.mg6.android.maps.extensions.GroundOverlay; import pl.mg6.android.maps.extensions.Marker; import pl.mg6.android.maps.extensions.Polygon; import pl.mg6.android.maps.extensions.Polyline; import pl.mg6.android.maps.extensions.TileOverlay; import pl.mg6.android.maps.extensions.lazy.LazyMarker; import pl.mg6.android.maps.extensions.lazy.LazyMarker.OnMarkerCreateListener; import android.location.Location; import android.view.View; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.LocationSource; import com.google.android.gms.maps.Projection; import com.google.android.gms.maps.UiSettings; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.CircleOptions; import com.google.android.gms.maps.model.GroundOverlayOptions; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.gms.maps.model.PolygonOptions; import com.google.android.gms.maps.model.PolylineOptions; import com.google.android.gms.maps.model.TileOverlayOptions; public class DelegatingGoogleMap implements GoogleMap, OnMarkerCreateListener { private GoogleMapWrapper real; private InfoWindowAdapter infoWindowAdapter; private OnCameraChangeListener onCameraChangeListener; private OnMarkerDragListener onMarkerDragListener; private Map<LazyMarker, DelegatingMarker> markers; private Map<com.google.android.gms.maps.model.Marker, LazyMarker> createdMarkers; private Map<com.google.android.gms.maps.model.Polyline, Polyline> polylines; private Map<com.google.android.gms.maps.model.Polygon, Polygon> polygons; private Map<com.google.android.gms.maps.model.Circle, Circle> circles; private Map<com.google.android.gms.maps.model.GroundOverlay, GroundOverlay> groundOverlays; private Map<com.google.android.gms.maps.model.TileOverlay, TileOverlay> tileOverlays; private Marker markerShowingInfoWindow; private ClusteringSettings clusteringSettings = new ClusteringSettings().enabled(false); private ClusteringStrategy clusteringStrategy = new NoClusteringStrategy(new ArrayList<DelegatingMarker>()); public DelegatingGoogleMap(com.google.android.gms.maps.GoogleMap real) { this.real = new GoogleMapWrapper(real); this.markers = new HashMap<LazyMarker, DelegatingMarker>(); this.createdMarkers = new HashMap<com.google.android.gms.maps.model.Marker, LazyMarker>(); this.polylines = new HashMap<com.google.android.gms.maps.model.Polyline, Polyline>(); this.polygons = new HashMap<com.google.android.gms.maps.model.Polygon, Polygon>(); this.circles = new HashMap<com.google.android.gms.maps.model.Circle, Circle>(); this.groundOverlays = new HashMap<com.google.android.gms.maps.model.GroundOverlay, GroundOverlay>(); this.tileOverlays = new HashMap<com.google.android.gms.maps.model.TileOverlay, TileOverlay>(); real.setInfoWindowAdapter(new DelegatingInfoWindowAdapter()); real.setOnCameraChangeListener(new DelegatingOnCameraChangeListener()); real.setOnMarkerDragListener(new DelegatingOnMarkerDragListener()); } @Override public Circle addCircle(CircleOptions circleOptions) { com.google.android.gms.maps.model.Circle realCircle = real.addCircle(circleOptions); Circle circle = new DelegatingCircle(realCircle, this); circles.put(realCircle, circle); return circle; } @Override public GroundOverlay addGroundOverlay(GroundOverlayOptions groundOverlayOptions) { com.google.android.gms.maps.model.GroundOverlay realGroundOverlay = real.addGroundOverlay(groundOverlayOptions); GroundOverlay groundOverlay = new DelegatingGroundOverlay(realGroundOverlay, this); groundOverlays.put(realGroundOverlay, groundOverlay); return groundOverlay; } @Override public Marker addMarker(MarkerOptions markerOptions) { boolean visible = markerOptions.isVisible(); markerOptions.visible(false); LazyMarker realMarker = new LazyMarker(real.getMap(), markerOptions, this); markerOptions.visible(visible); DelegatingMarker marker = new DelegatingMarker(realMarker, this); markers.put(realMarker, marker); clusteringStrategy.onAdd(marker); marker.setVisible(visible); return marker; } @Override public Polygon addPolygon(PolygonOptions polygonOptions) { com.google.android.gms.maps.model.Polygon realPolygon = real.addPolygon(polygonOptions); Polygon polygon = new DelegatingPolygon(realPolygon, this); polygons.put(realPolygon, polygon); return polygon; } @Override public Polyline addPolyline(PolylineOptions polylineOptions) { com.google.android.gms.maps.model.Polyline realPolyline = real.addPolyline(polylineOptions); Polyline polyline = new DelegatingPolyline(realPolyline, this); polylines.put(realPolyline, polyline); return polyline; } @Override public TileOverlay addTileOverlay(TileOverlayOptions tileOverlayOptions) { com.google.android.gms.maps.model.TileOverlay realTileOverlay = real.addTileOverlay(tileOverlayOptions); TileOverlay tileOverlay = new DelegatingTileOverlay(realTileOverlay, this); tileOverlays.put(realTileOverlay, tileOverlay); return tileOverlay; } @Override public void animateCamera(CameraUpdate cameraUpdate, CancelableCallback cancelableCallback) { real.animateCamera(cameraUpdate, cancelableCallback); } @Override public void animateCamera(CameraUpdate cameraUpdate, int time, CancelableCallback cancelableCallback) { real.animateCamera(cameraUpdate, time, cancelableCallback); } @Override public void animateCamera(CameraUpdate cameraUpdate) { real.animateCamera(cameraUpdate); } @Override public void clear() { real.clear(); markers.clear(); createdMarkers.clear(); polylines.clear(); polygons.clear(); circles.clear(); groundOverlays.clear(); tileOverlays.clear(); clusteringStrategy.cleanup(); } @Override public CameraPosition getCameraPosition() { return real.getCameraPosition(); } @Override public List<Marker> getDisplayedMarkers() { List<Marker> displayedMarkers = clusteringStrategy.getDisplayedMarkers(); if (displayedMarkers == null) { displayedMarkers = getMarkers(); Iterator<Marker> iterator = displayedMarkers.iterator(); while (iterator.hasNext()) { Marker m = iterator.next(); if (!m.isVisible()) { iterator.remove(); } } } return displayedMarkers; } @Override public int getMapType() { return real.getMapType(); } @Override public List<Circle> getCircles() { return new ArrayList<Circle>(circles.values()); } @Override public List<GroundOverlay> getGroundOverlays() { return new ArrayList<GroundOverlay>(groundOverlays.values()); } @Override public List<Marker> getMarkers() { return new ArrayList<Marker>(markers.values()); } @Override public Marker getMarkerShowingInfoWindow() { if (markerShowingInfoWindow != null && !markerShowingInfoWindow.isInfoWindowShown()) { markerShowingInfoWindow = null; } return markerShowingInfoWindow; } @Override public List<Polygon> getPolygons() { return new ArrayList<Polygon>(polygons.values()); } @Override public List<Polyline> getPolylines() { return new ArrayList<Polyline>(polylines.values()); } @Override public List<TileOverlay> getTileOverlays() { return new ArrayList<TileOverlay>(tileOverlays.values()); } @Override public float getMaxZoomLevel() { return real.getMaxZoomLevel(); } @Override public float getMinZoomLevel() { return real.getMinZoomLevel(); } @Override public float getMinZoomLevelNotClustered(Marker marker) { return clusteringStrategy.getMinZoomLevelNotClustered(marker); } @Override public Location getMyLocation() { return real.getMyLocation(); } @Override public Projection getProjection() { return real.getProjection().getProjection(); } @Override public UiSettings getUiSettings() { return real.getUiSettings(); } @Override public boolean isIndoorEnabled() { return real.isIndoorEnabled(); } @Override public boolean isMyLocationEnabled() { return real.isMyLocationEnabled(); } @Override public boolean isTrafficEnabled() { return real.isTrafficEnabled(); } @Override public void moveCamera(CameraUpdate cameraUpdate) { real.moveCamera(cameraUpdate); } @Override public void setClustering(ClusteringSettings clusteringSettings) { if (clusteringSettings == null) { clusteringSettings = new ClusteringSettings().enabled(false); } if (!this.clusteringSettings.equals(clusteringSettings)) { this.clusteringSettings = clusteringSettings; clusteringStrategy.cleanup(); ArrayList<DelegatingMarker> list = new ArrayList<DelegatingMarker>(markers.values()); if (clusteringSettings.isEnabled()) { clusteringStrategy = new GridClusteringStrategy(clusteringSettings, real, list, new ClusterRefresher()); } else if (clusteringSettings.isAddMarkersDynamically()) { clusteringStrategy = new DynamicNoClusteringStrategy(real, list); } else { clusteringStrategy = new NoClusteringStrategy(list); } } } @Override public boolean setIndoorEnabled(boolean indoorEnabled) { return real.setIndoorEnabled(indoorEnabled); } @Override public void setInfoWindowAdapter(final InfoWindowAdapter infoWindowAdapter) { this.infoWindowAdapter = infoWindowAdapter; } @Override public void setLocationSource(LocationSource locationSource) { real.setLocationSource(locationSource); } @Override public void setMapType(int mapType) { real.setMapType(mapType); } @Override public void setMyLocationEnabled(boolean myLocationEnabled) { real.setMyLocationEnabled(myLocationEnabled); } @Override public void setOnCameraChangeListener(OnCameraChangeListener onCameraChangeListener) { this.onCameraChangeListener = onCameraChangeListener; } @Override public void setOnInfoWindowClickListener(OnInfoWindowClickListener onInfoWindowClickListener) { com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener realOnInfoWindowClickListener = null; if (onInfoWindowClickListener != null) { realOnInfoWindowClickListener = new DelegatingOnInfoWindowClickListener(onInfoWindowClickListener); } real.setOnInfoWindowClickListener(realOnInfoWindowClickListener); } @Override public void setOnMapClickListener(OnMapClickListener onMapClickListener) { real.setOnMapClickListener(onMapClickListener); } @Override public void setOnMapLongClickListener(OnMapLongClickListener onMapLongClickListener) { real.setOnMapLongClickListener(onMapLongClickListener); } @Override public void setOnMarkerClickListener(OnMarkerClickListener onMarkerClickListener) { com.google.android.gms.maps.GoogleMap.OnMarkerClickListener realOnMarkerClickListener = null; if (onMarkerClickListener != null) { realOnMarkerClickListener = new DelegatingOnMarkerClickListener(onMarkerClickListener); } real.setOnMarkerClickListener(realOnMarkerClickListener); } @Override public void setOnMarkerDragListener(OnMarkerDragListener onMarkerDragListener) { this.onMarkerDragListener = onMarkerDragListener; } @Override public void setOnMyLocationChangeListener(OnMyLocationChangeListener onMyLocationChangeListener) { real.setOnMyLocationChangeListener(onMyLocationChangeListener); } @Override public void setTrafficEnabled(boolean trafficEnabled) { real.setTrafficEnabled(trafficEnabled); } @Override public void stopAnimation() { real.stopAnimation(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof DelegatingGoogleMap)) { return false; } DelegatingGoogleMap other = (DelegatingGoogleMap) o; return real.equals(other.real); } @Override public int hashCode() { return real.hashCode(); } @Override public String toString() { return real.toString(); } void remove(com.google.android.gms.maps.model.Polyline polyline) { polylines.remove(polyline); } void remove(com.google.android.gms.maps.model.Polygon polygon) { polygons.remove(polygon); } void remove(com.google.android.gms.maps.model.Circle circle) { circles.remove(circle); } void remove(com.google.android.gms.maps.model.GroundOverlay groundOverlay) { groundOverlays.remove(groundOverlay); } void remove(com.google.android.gms.maps.model.TileOverlay tileOverlay) { tileOverlays.remove(tileOverlay); } void onRemove(DelegatingMarker marker) { markers.remove(marker.getReal()); createdMarkers.remove(marker.getReal().getMarker()); clusteringStrategy.onRemove(marker); } void onPositionChange(DelegatingMarker marker) { clusteringStrategy.onPositionChange(marker); } void onVisibilityChangeRequest(DelegatingMarker marker, boolean visible) { clusteringStrategy.onVisibilityChangeRequest(marker, visible); } @Override public void onMarkerCreate(LazyMarker marker) { createdMarkers.put(marker.getMarker(), marker); } private class DelegatingOnCameraChangeListener implements com.google.android.gms.maps.GoogleMap.OnCameraChangeListener { @Override public void onCameraChange(CameraPosition cameraPosition) { clusteringStrategy.onCameraChange(cameraPosition); if (onCameraChangeListener != null) { onCameraChangeListener.onCameraChange(cameraPosition); } } } private class DelegatingInfoWindowAdapter implements com.google.android.gms.maps.GoogleMap.InfoWindowAdapter { @Override public View getInfoWindow(com.google.android.gms.maps.model.Marker marker) { markerShowingInfoWindow = map(marker); if (infoWindowAdapter != null) { return infoWindowAdapter.getInfoWindow(markerShowingInfoWindow); } return null; } @Override public View getInfoContents(com.google.android.gms.maps.model.Marker marker) { if (infoWindowAdapter != null) { return infoWindowAdapter.getInfoContents(map(marker)); } return null; } } private class DelegatingOnInfoWindowClickListener implements com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener { private OnInfoWindowClickListener onInfoWindowClickListener; private DelegatingOnInfoWindowClickListener(OnInfoWindowClickListener onInfoWindowClickListener) { this.onInfoWindowClickListener = onInfoWindowClickListener; } @Override public void onInfoWindowClick(com.google.android.gms.maps.model.Marker marker) { onInfoWindowClickListener.onInfoWindowClick(map(marker)); } } private class DelegatingOnMarkerClickListener implements com.google.android.gms.maps.GoogleMap.OnMarkerClickListener { private OnMarkerClickListener onMarkerClickListener; private DelegatingOnMarkerClickListener(OnMarkerClickListener onMarkerClickListener) { this.onMarkerClickListener = onMarkerClickListener; } @Override public boolean onMarkerClick(com.google.android.gms.maps.model.Marker marker) { return onMarkerClickListener.onMarkerClick(map(marker)); } } private class DelegatingOnMarkerDragListener implements com.google.android.gms.maps.GoogleMap.OnMarkerDragListener { @Override public void onMarkerDragStart(com.google.android.gms.maps.model.Marker marker) { if (onMarkerDragListener != null) { onMarkerDragListener.onMarkerDragStart(map(marker)); } } @Override public void onMarkerDrag(com.google.android.gms.maps.model.Marker marker) { if (onMarkerDragListener != null) { onMarkerDragListener.onMarkerDrag(map(marker)); } } @Override public void onMarkerDragEnd(com.google.android.gms.maps.model.Marker marker) { LazyMarker lazy = createdMarkers.get(marker); DelegatingMarker delegating = markers.get(lazy); clusteringStrategy.onPositionChange(delegating); if (onMarkerDragListener != null) { onMarkerDragListener.onMarkerDragEnd(map(marker)); } } } private Marker map(com.google.android.gms.maps.model.Marker marker) { Marker cluster = clusteringStrategy.map(marker); if (cluster != null) { return cluster; } LazyMarker lazy = createdMarkers.get(marker); DelegatingMarker delegating = markers.get(lazy); return delegating; } }