package org.vaadin.hezamu.googlemapwidget;
import java.awt.geom.Point2D;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.vaadin.hezamu.googlemapwidget.overlay.BasicMarkerSource;
import org.vaadin.hezamu.googlemapwidget.overlay.InfoWindowTab;
import org.vaadin.hezamu.googlemapwidget.overlay.Marker;
import org.vaadin.hezamu.googlemapwidget.overlay.MarkerSource;
import org.vaadin.hezamu.googlemapwidget.overlay.PolyOverlay;
import org.vaadin.hezamu.googlemapwidget.overlay.Polygon;
import org.vaadin.hezamu.googlemapwidget.widgetset.client.ui.VGoogleMap;
import com.vaadin.Application;
import com.vaadin.terminal.ApplicationResource;
import com.vaadin.terminal.DownloadStream;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.ClientWidget;
/**
* Server side component for the VGoogleMap widget.
*/
@ClientWidget(VGoogleMap.class)
public class GoogleMap extends AbstractComponent {
private static final long serialVersionUID = -7237970245561106947L;
public enum MapControl {
SmallMapControl, HierarchicalMapTypeControl, LargeMapControl, MapTypeControl, MenuMapTypeControl, OverviewMapControl, ScaleControl, SmallZoomControl
}
private Point2D.Double center;
private Point2D.Double boundsNE;
private Point2D.Double boundsSW;
private int zoom;
private final List<MapMoveListener> moveListeners = new ArrayList<MapMoveListener>();
private final List<MapClickListener> mapClickListeners = new ArrayList<MapClickListener>();
private final List<MarkerClickListener> markerListeners = new ArrayList<MarkerClickListener>();
private final List<MarkerMovedListener> markerMovedListeners = new ArrayList<MarkerMovedListener>();
private MarkerSource markerSource = null;
private Marker clickedMarker = null;
private boolean closeInfoWindow = false;
private final Map<Long, PolyOverlay> overlays = new HashMap<Long, PolyOverlay>();
// private boolean overlaysChanged = false;
private boolean scrollWheelZoomEnabled = true;
private boolean clearMapTypes = false;
private final List<MapControl> controls = new ArrayList<MapControl>();
private final List<CustomMapType> mapTypes = new ArrayList<CustomMapType>();
private boolean mapTypesChanged = false;
private boolean reportMapBounds = false;
private final ApplicationResource markerResource = new ApplicationResource() {
private static final long serialVersionUID = -6926454922185543547L;
public Application getApplication() {
return GoogleMap.this.getApplication();
}
public int getBufferSize() {
return markerSource.getMarkerJSON().length;
}
public long getCacheTime() {
return -1;
}
public String getFilename() {
return "markersource.txt";
}
public DownloadStream getStream() {
return new DownloadStream(new ByteArrayInputStream(
markerSource.getMarkerJSON()), getMIMEType(), getFilename());
}
public String getMIMEType() {
return "text/plain";
}
};
private String apiKey = "";
private int clientLogLevel = 0;
/**
* Construct a new instance of the map with given size.
*
* @param application
* @link Application owning this instance.
*/
public GoogleMap(Application application) {
application.addResource(markerResource);
// Greewich Royal Observatory
center = new Point2D.Double(-0.001475, 51.477811);
zoom = 14;
}
/**
* Construct a new instance of the map with given parameters.
*
* @param application
* @link Application owning this instance.
* @param center
* center of the map as a {@link Point2D.Double}
* @param zoom
* initial zoom level of the map
*/
public GoogleMap(Application application, Point2D.Double center, int zoom) {
this(application);
this.center = center;
this.zoom = zoom;
}
/**
* Construct a new instance of the map with given size.
*
* @param application
* @link Application owning this instance.
* @param apiKey
* - the API key to be used for Google Maps
*/
public GoogleMap(Application application, String apiKey) {
application.addResource(markerResource);
this.apiKey = apiKey;
// Greewich Royal Observatory
center = new Point2D.Double(-0.001475, 51.477811);
zoom = 14;
}
/**
* Construct a new instance of the map with given parameters.
*
* @param application
* @link Application owning this instance.
* @param center
* center of the map as a {@link Point2D.Double}
* @param zoom
* initial zoom level of the map
* @param apiKey
* - the API key to be used for Google Maps
*/
public GoogleMap(Application application, Point2D.Double center, int zoom,
String apiKey) {
this(application);
this.apiKey = apiKey;
this.center = center;
this.zoom = zoom;
}
@Override
public void paintContent(PaintTarget target) throws PaintException {
super.paintContent(target);
target.addVariable(this, "center_lat", center.y);
target.addVariable(this, "center_lng", center.x);
target.addVariable(this, "zoom", zoom);
target.addVariable(this, "swze", scrollWheelZoomEnabled);
target.addAttribute("apikey", apiKey);
target.addAttribute("loglevel", clientLogLevel);
for (MapControl control : controls) {
target.addAttribute(control.name(), true);
}
// TODO this feels like a kludge, but unsure how to implement correctly
if (clickedMarker != null) {
target.addAttribute("marker", clickedMarker.getId().toString());
target.startTag("tabs");
InfoWindowTab[] tabs = clickedMarker.getInfoWindowContent();
for (int i = 0; i < tabs.length; i++) {
target.startTag("tab");
if (tabs.length > 1) {
target.addAttribute("selected", tabs[i].isSelected());
target.addAttribute("label", tabs[i].getLabel());
}
tabs[i].getContent().paint(target);
target.endTag("tab");
}
target.endTag("tabs");
clickedMarker = null;
} else if (markerSource != null) {
target.addAttribute("markerRes", markerResource);
}
if (closeInfoWindow) {
target.addAttribute("closeInfoWindow", true);
closeInfoWindow = false;
}
// if (overlaysChanged) {
target.startTag("overlays");
for (PolyOverlay poly : overlays.values()) {
target.startTag("o");
target.addAttribute("id", poly.getId());
// Encode polyline points as a string attribute
StringBuilder sb = new StringBuilder();
Point2D.Double[] points = poly.getPoints();
for (int i = 0; i < points.length; i++) {
if (i > 0) {
sb.append(" ");
}
sb.append("" + points[i].y + "," + points[i].x);
}
target.addAttribute("points", sb.toString());
target.addAttribute("color", poly.getColor());
target.addAttribute("weight", poly.getWeight());
target.addAttribute("opacity", poly.getOpacity());
target.addAttribute("clickable", poly.isClickable());
if (poly instanceof Polygon) {
Polygon polygon = (Polygon) poly;
target.addAttribute("fillcolor", polygon.getFillColor());
target.addAttribute("fillopacity", polygon.getFillOpacity());
}
target.endTag("o");
}
target.endTag("overlays");
//
// overlaysChanged = false;
// }
if (clearMapTypes) {
target.addAttribute("clearMapTypes", true);
clearMapTypes = false;
}
if (mapTypesChanged) {
target.startTag("mapTypes");
for (CustomMapType mapType : mapTypes) {
mapType.paintContent(target);
}
target.endTag("mapTypes");
mapTypesChanged = false;
}
if (reportMapBounds) {
target.addAttribute("reportBounds", true);
reportMapBounds = false;
}
}
/**
* Receive and handle events and other variable changes from the client.
*
* {@inheritDoc}
*/
@Override
public void changeVariables(Object source, Map<String, Object> variables) {
super.changeVariables(source, variables);
if (variables.containsKey("click_pos")) {
fireClickEvent(variables.get("click_pos"));
requestRepaint();
}
boolean moveEvent = false;
Integer intVar;
if ((intVar = (Integer) variables.get("zoom")) != null) {
zoom = intVar;
moveEvent = true;
}
String stringVar;
if ((stringVar = (String) variables.get("center")) != null
&& !stringVar.trim().equals("")) {
center = GoogleMap.strToLL(stringVar);
moveEvent = true;
}
if ((stringVar = (String) variables.get("bounds_ne")) != null
&& !stringVar.trim().equals("")) {
boundsNE = GoogleMap.strToLL(stringVar);
moveEvent = true;
}
if ((stringVar = (String) variables.get("bounds_sw")) != null
&& !stringVar.trim().equals("")) {
boundsSW = GoogleMap.strToLL(stringVar);
moveEvent = true;
}
if (moveEvent) {
fireMoveEvent();
}
if (variables.containsKey("marker")) {
clickedMarker = markerSource.getMarker(variables.get("marker")
.toString());
if (clickedMarker != null) {
fireMarkerClickedEvent(clickedMarker);
if (clickedMarker.getInfoWindowContent() != null) {
requestRepaint();
}
}
}
if (variables.containsKey("markerMovedId")) {
String markerID = variables.get("markerMovedId").toString()
.replaceAll("\"", "");
List<Marker> markers = markerSource.getMarkers();
for (Marker mark : markers) {
if (mark.getId() == Long.parseLong(markerID)) {
double lat = new Double(variables.get("markerMovedLat")
.toString());
double lng = new Double(variables.get("markerMovedLong")
.toString());
mark.getLatLng().setLocation(lng, lat);
fireMarkerMovedEvent(mark);
break;
}
}
}
}
private void fireMoveEvent() {
for (MapMoveListener listener : moveListeners) {
listener.mapMoved(zoom, center, boundsNE, boundsSW);
}
}
private void fireClickEvent(Object object) {
Point2D.Double clickPos = GoogleMap.strToLL(object.toString());
for (MapClickListener listener : mapClickListeners) {
listener.mapClicked(clickPos);
}
}
private void fireMarkerClickedEvent(Marker clickedMarker) {
for (MarkerClickListener m : markerListeners) {
m.markerClicked(clickedMarker);
}
}
private void fireMarkerMovedEvent(Marker movedMarker) {
for (MarkerMovedListener m : markerMovedListeners) {
m.markerMoved(movedMarker);
}
}
/**
* Interface for listening map move and zoom events.
*
* @author Henri Muurimaa
*/
public interface MapMoveListener {
/**
* Handle a MapMoveEvent.
*
* @param newZoomLevel
* New zoom level
* @param newCenter
* New center coordinates
* @param boundsNE
* Coordinates of the north-east corner of the map
* @param boundsSW
* Coordinates of the south-west corner of the map
*/
public void mapMoved(int newZoomLevel, Point2D.Double newCenter,
Point2D.Double boundsNE, Point2D.Double boundsSW);
}
/**
* Interface for listening map click events.
*
* @author Henri Muurimaa
*/
public interface MapClickListener {
/**
* Handle a MapClickEvent.
*
* @param clickPos
* coordinates of the click event.
*
*/
public void mapClicked(Point2D.Double clickPos);
}
/**
* Interface for listening marker click events.
*
*/
public interface MarkerClickListener {
/**
* Handle a MarkerClickEvent.
*
* @param clickedMarker
* the marker that was clicked.
*
*/
public void markerClicked(Marker clickedMarker);
}
/**
* Interface for listening marker move events.
*
*/
public interface MarkerMovedListener {
/**
* Handle a MarkerMovedEvent.
*
* @param movedMarker
* the marker that was moved.
*
*/
public void markerMoved(Marker movedMarker);
}
/**
* Register a new {@link MapClickListener}.
*
* @param listener
* new {@link MapClickListener} to register
*/
public void addListener(MapClickListener listener) {
if (!mapClickListeners.contains(listener)) {
mapClickListeners.add(listener);
}
}
/**
* Deregister a {@link MapClickListener}.
*
* @param listener
* {@link MapClickListener} to deregister
*/
public void removeListener(MapClickListener listener) {
if (mapClickListeners.contains(listener)) {
mapClickListeners.remove(listener);
}
}
/**
* Register a new {@link MapMoveListener}.
*
* @param listener
* new {@link MapMoveListener} to register
*/
public void addListener(MapMoveListener listener) {
if (!moveListeners.contains(listener)) {
moveListeners.add(listener);
}
}
/**
* Register a new {@link MarkerMovedListener}.
*
* NOTE!! The marker that is clicked MUST have some information window
* content! This is due to the implementation of the Widget, as the marker
* click events do not propagate if there is not a information window
* opened.
*
* @param listener
* new {@link MarkerClickListener} to register
*/
public void addListener(MarkerClickListener listener) {
if (!markerListeners.contains(listener)) {
markerListeners.add(listener);
}
}
/**
* Register a new {@link MarkerMovedListener}.
*
*
*
* @param listener
* new {@link MarkerMovedListener} to register
*/
public void addListener(MarkerMovedListener listener) {
if (!markerMovedListeners.contains(listener)) {
markerMovedListeners.add(listener);
}
}
/**
* Deregister a {@link MapMoveListener}.
*
* @param listener
* {@link MapMoveListener} to deregister
*/
public void removeListener(MapMoveListener listener) {
if (moveListeners.contains(listener)) {
moveListeners.remove(listener);
}
}
/**
* Deregister a {@link MarkerClickListener}.
*
* @param listener
* {@link MarkerClickListener} to deregister
*/
public void removeListener(MarkerClickListener listener) {
if (markerListeners.contains(listener)) {
markerListeners.remove(listener);
}
}
/**
* Deregister a {@link MarkerMovedListener}.
*
* @param listener
* the {@link MarkerMovedListener} to deregister
*/
public void removeListener(MarkerMovedListener listener) {
if (markerMovedListeners.contains(listener)) {
markerMovedListeners.remove(listener);
}
}
/**
* Get current center coordinates of the map.
*
* @return
*/
public Point2D.Double getCenter() {
return center;
}
/**
* Set the current center coordinates of the map. This method can be used to
* pan the map programmatically.
*
* @param center
* the new center coordinates
*/
public void setCenter(Point2D.Double center) {
this.center = center;
requestRepaint();
}
/**
* Get the current zoom level of the map.
*
* @return the current zoom level
*/
public int getZoom() {
return zoom;
}
/**
* Set the zoom level of the map. This method can be used to zoom the map
* programmatically.
*
* @param zoom
*/
public void setZoom(int zoom) {
this.zoom = zoom;
requestRepaint();
}
/**
* Set the level of verbosity the client side uses for tracing or displaying
* error messages.
*
* @param level
*/
public void setClientLogLevel(int level) {
this.clientLogLevel = level;
requestRepaint();
}
/**
* Get the level of verbosity the client side uses for tracing or displaying
* error messages.
*/
public int getClientLogLevel() {
return clientLogLevel;
}
/**
* Get the coordinates of the north-east corner of the map.
*
* @return
*/
public Point2D.Double getBoundsNE() {
return boundsNE;
}
/**
* Get the coordinates of the south-west corner of the map.
*
* @return
*/
public Point2D.Double getBoundsSW() {
return boundsSW;
}
/**
* Set the {@link MarkerSource} for the map.
*
* @param markerSource
*/
public void setMarkerSource(MarkerSource markerSource) {
this.markerSource = markerSource;
}
/**
* Close the currently open info window, if any.
*/
public void closeInfoWindow() {
closeInfoWindow = true;
requestRepaint();
}
/**
* Add a new {@link PolyOverlay} to the map. Does nothing if the overlay
* already exist on the map.
*
* @param overlay
* {@link PolyOverlay} to add
*
* @return True if the overlay was added.
*/
public boolean addPolyOverlay(PolyOverlay overlay) {
if (!overlays.containsKey(overlay.getId())) {
overlays.put(overlay.getId(), overlay);
// overlaysChanged = true;
requestRepaint();
return true;
}
return false;
}
/**
* Update a {@link PolyOverlay} on the map. Does nothing if the overlay does
* not exist on the map.
*
* @param overlay
* {@link PolyOverlay} to update
*
* @return True if the overlay was updated.
*/
public boolean updateOverlay(PolyOverlay overlay) {
if (overlays.containsKey(overlay.getId())) {
overlays.put(overlay.getId(), overlay);
// overlaysChanged = true;
requestRepaint();
return true;
}
return false;
}
/**
* Remove a {@link PolyOverlay} from the map. Does nothing if the overlay
* does not exist on the map.
*
* @param overlay
* {@link PolyOverlay} to remove
*
* @return True if the overlay was removed.
*/
public boolean removeOverlay(PolyOverlay overlay) {
if (overlays.containsKey(overlay.getId())) {
overlays.remove(overlay.getId());
// overlaysChanged = true;
requestRepaint();
return true;
}
return false;
}
/**
* Get the collection of {@link PolyOverlay}s currently in the map.
*
* @return a {@link Collection} of overlays.
*/
public Collection<PolyOverlay> getOverlays() {
return overlays.values();
}
private static Point2D.Double strToLL(String latLngStr) {
if (latLngStr == null) {
return null;
}
String nums[] = latLngStr.split(", ");
if (nums.length != 2) {
return null;
}
double lat = Double.parseDouble(nums[0].substring(1));
double lng = Double.parseDouble(nums[1].substring(0,
nums[1].length() - 1));
return new Point2D.Double(lng, lat);
}
/**
* Add a Marker to the current MarkerSource. If the map has no marker source
* a new {@link BasicMarkerSource} is created.
*
* @param marker
* Marker to add
*/
public void addMarker(Marker marker) {
if (markerSource == null) {
markerSource = new BasicMarkerSource();
}
markerSource.addMarker(marker);
requestRepaint();
}
/**
* Removes the marker from the map
*
* @param marker
*/
public void removeMarker(Marker marker) {
if (markerSource != null) {
markerSource.getMarkers().remove(marker);
requestRepaint();
}
}
public void removeAllMarkers() {
if (markerSource != null) {
markerSource.getMarkers().clear();
requestRepaint();
}
}
public void setScrollWheelZoomEnabled(boolean isEnabled) {
scrollWheelZoomEnabled = isEnabled;
}
public boolean isScrollWheelZoomEnabled() {
return scrollWheelZoomEnabled;
}
public boolean addControl(MapControl control) {
if (!controls.contains(control)) {
controls.add(control);
return true;
}
return false;
}
public boolean hasControl(MapControl control) {
return controls.contains(control);
}
public boolean removeControl(MapControl control) {
if (controls.contains(control)) {
controls.remove(control);
return true;
}
return false;
}
public void addMapType(String name, int minZoom, int maxZoom,
String copyright, String tileUrl, boolean isPng, double opacity) {
mapTypes.add(new CustomMapType(name, minZoom, maxZoom, copyright,
tileUrl, isPng, opacity));
mapTypesChanged = true;
requestRepaint();
}
public void clearMapTypes() {
mapTypes.clear();
clearMapTypes = true;
requestRepaint();
}
public void reportMapBounds() {
reportMapBounds = true;
requestRepaint();
}
class CustomMapType {
private final double opacity;
private final String tileUrl;
private final boolean isPng;
private final int minZoom;
private final int maxZoom;
private final String copyright;
private final String name;
public CustomMapType(String name, int minZoom, int maxZoom,
String copyright, String tileUrl, boolean isPng, double opacity) {
this.name = name;
this.minZoom = minZoom;
this.maxZoom = maxZoom;
this.copyright = copyright;
this.tileUrl = tileUrl;
this.isPng = isPng;
this.opacity = opacity;
}
public void paintContent(PaintTarget target) throws PaintException {
target.startTag("maptype");
target.addAttribute("name", name);
target.addAttribute("minZoom", minZoom);
target.addAttribute("maxZoom", maxZoom);
target.addAttribute("copyright", copyright);
target.addAttribute("tileUrl", tileUrl);
target.addAttribute("isPng", isPng);
target.addAttribute("opacity", opacity);
target.endTag("maptype");
}
public double getOpacity() {
return opacity;
}
public String getTileUrl() {
return tileUrl;
}
public boolean isPng() {
return isPng;
}
public int getMinZoom() {
return minZoom;
}
public int getMaxZoom() {
return maxZoom;
}
public String getCopyright() {
return copyright;
}
}
}