com.dlsc.gmapsfx.javascript.object.GoogleMap Maven / Gradle / Ivy
/*
* Copyright 2014 Lynden, Inc.
*
* 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 com.dlsc.gmapsfx.javascript.object;
import com.dlsc.gmapsfx.javascript.JavascriptObject;
import com.dlsc.gmapsfx.javascript.JavascriptRuntime;
import com.dlsc.gmapsfx.javascript.event.EventHandlers;
import com.dlsc.gmapsfx.javascript.event.GFXEventHandler;
import com.dlsc.gmapsfx.javascript.event.MapStateEventType;
import com.dlsc.gmapsfx.javascript.event.MouseEventHandler;
import com.dlsc.gmapsfx.javascript.event.StateEventHandler;
import com.dlsc.gmapsfx.javascript.event.UIEventHandler;
import com.dlsc.gmapsfx.javascript.event.UIEventType;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Point2D;
import javafx.util.Callback;
import netscape.javascript.JSObject;
/**
*
* @author Rob Terpilowski
*/
public class GoogleMap extends JavascriptObject {
private boolean userPromptedZoomChange;
private boolean mapPromptedZoomChange;
protected MapOptions options;
protected static String divArg = "document.getElementById('map-canvas')";
private ReadOnlyObjectWrapper center;
private IntegerProperty zoom;
private ReadOnlyObjectWrapper bounds;
private final EventHandlers jsHandlers = new EventHandlers();
private boolean registeredOnJS;
private Set markers;
public GoogleMap() {
super(GMapObjectType.MAP, divArg);
initialize();
}
public GoogleMap(MapOptions mapOptions) {
super(GMapObjectType.MAP, divArg, mapOptions);
initialize();
}
protected void initialize(){
zoom = new SimpleIntegerProperty(internalGetZoom());
addStateEventHandler(MapStateEventType.zoom_changed, () -> {
if (!userPromptedZoomChange) {
mapPromptedZoomChange = true;
zoom.set(internalGetZoom());
mapPromptedZoomChange = false;
}
});
zoom.addListener((ObservableValue obs, Number o, Number n) -> {
if (!mapPromptedZoomChange) {
userPromptedZoomChange = true;
internalSetZoom(n.intValue());
userPromptedZoomChange = false;
}
});
center = new ReadOnlyObjectWrapper<>(getCenter());
addStateEventHandler(MapStateEventType.center_changed, () -> {
center.set(getCenter());
});
}
public void setZoom(int zoom) {
zoomProperty().set(zoom);
}
public int getZoom() {
return zoomProperty().get();
}
private int internalGetZoom() {
return (int) invokeJavascript("getZoom");
}
private void internalSetZoom(int zoom) {
invokeJavascript("setZoom", zoom);
}
public void showDirectionsPane() {
JavascriptRuntime.getInstance().execute("showDirections()");
}
public void hideDirectionsPane() {
JavascriptRuntime.getInstance().execute("hideDirections()");
}
public IntegerProperty zoomProperty() {
return zoom;
}
// This method was calling setCenter anyway, so would never have worked.
// public LatLong getLatLong() {
// return getProperty("setCenter", LatLong.class);
// }
public final ReadOnlyObjectProperty centerProperty() {
return center.getReadOnlyProperty();
}
public LatLong getCenter() {
return new LatLong((JSObject) invokeJavascript("getCenter"));
}
public void setCenter(LatLong latLong) {
invokeJavascript("setCenter", latLong);
}
/**
* Returns the LatLongBounds of the visual area. Note: on zoom changes the
* bounds are reset after the zoom event is fired, which can cause
* unexpected results.
*
* @return
*/
public LatLongBounds getBounds() {
return invokeJavascriptReturnValue("getBounds", LatLongBounds.class);
}
/**
* Moves the map to ensure the given bounds fit within the viewport.
*
* Note that the Google Maps API will add a buffer around this value, so
* assuming you can store this and use it to later restore the view will
* give incorrect results. Calling map.fitBounds(map.getBounds()); will
* result in the map gradually zooming outward.
*
*
* @param bounds
*/
public void fitBounds(LatLongBounds bounds) {
invokeJavascript("fitBounds", bounds);
}
public void panToBounds(LatLongBounds bounds) {
invokeJavascript("panToBounds", bounds);
}
/**
* A property tied to the map, updated when the idle state event is fired.
*
* @return
*/
public final ReadOnlyObjectProperty boundsProperty() {
if (bounds == null) {
bounds = new ReadOnlyObjectWrapper<>(getBounds());
addStateEventHandler(MapStateEventType.idle, () -> {
bounds.set(getBounds());
});
}
return bounds.getReadOnlyProperty();
}
public void setHeading(double heading) {
invokeJavascript("setHeading", heading);
}
public double getHeading() {
return invokeJavascriptReturnValue("getHeading", Double.class);
}
/**
* Adds the supplied marker to the map.
*
* @param marker
*/
public void addMarker(Marker marker) {
if (markers == null) {
markers = new HashSet<>();
}
markers.add(marker);
marker.setMap(this);
}
/**
* Removes the supplied marker from the map.
*
* @param marker
*/
public void removeMarker(Marker marker) {
if (markers != null && markers.contains(marker)) {
markers.remove(marker);
}
marker.setMap(null);
}
/**
* Removes all of the markers from the map.
*
*/
public void clearMarkers() {
if (markers != null && !markers.isEmpty()) {
markers.forEach((m) -> {
m.setMap(null);
});
markers.clear();
}
}
/**
* Adds all of the markers in the supplied collection to the map. Existing
* markers, if any, are retained.
*
* @param col
*/
public void addMarkers(Collection col) {
if (markers == null) {
markers = new HashSet<>(col);
} else {
markers.addAll(col);
}
col.forEach((m) -> {
m.setMap(this);
});
}
public void addMarkers(Collection col, UIEventType type, Callback h) {
if (markers == null) {
markers = new HashSet<>(col);
} else {
markers.addAll(col);
}
col.forEach((m) -> {
m.setMap(this);
addUIEventHandler(m, type, h.call(m));
});
}
/**
* Removes the markers in the supplied collection from the map.
*
* @param col
*/
public void removeMarkers(Collection col) {
if (markers != null && !markers.isEmpty()) {
markers.removeAll(col);
col.forEach((m) -> {
m.setMap(null);
});
}
}
/**
* Sets the map type. This is equivalent to the javascript method
* setMapTypeId.
*
* @param type
*/
public void setMapType(MapTypeIdEnum type) {
invokeJavascript("setMapTypeId", type);
}
public void addMapShape(MapShape shape) {
shape.setMap(this);
}
public void removeMapShape(MapShape shape) {
shape.setMap(null);
}
public Projection getProjection() {
Object obj = invokeJavascript("getProjection");
return (obj == null) ? null : new Projection((JSObject) obj);
}
/**
* Pans the map by the supplied values.
*
* @param x delta x value in pixels.
* @param y delta y value in pixels.
*/
public void panBy(double x, double y) {
// System.out.println("panBy x: " + x + ", y: " + y);
invokeJavascript("panBy", new Object[]{x, y});
}
/**
* Pans the map to the specified latitude and longitude.
*
* @param latLong
*/
public void panTo(LatLong latLong) {
invokeJavascript("panTo", latLong);
}
/**
* Returns the screen point for the provided LatLong. Note: Unexpected
* results can be obtained if this method is called as a result of a zoom
* change, as the zoom event is fired before the bounds are updated, and
* bounds need to be used to obtain the answer!
*
* One workaround is to only operate off bounds_changed events.
*
* @param loc
* @return
*/
public Point2D fromLatLngToPoint(LatLong loc) {
// System.out.println("GoogleMap.fromLatLngToPoint loc: " + loc);
Projection proj = getProjection();
//System.out.println("map.fromLatLngToPoint Projection: " + proj);
LatLongBounds llb = getBounds();
// System.out.println("GoogleMap.fromLatLngToPoint Bounds: " + llb);
GMapPoint topRight = proj.fromLatLngToPoint(llb.getNorthEast());
// System.out.println("GoogleMap.fromLatLngToPoint topRight: " + topRight);
GMapPoint bottomLeft = proj.fromLatLngToPoint(llb.getSouthWest());
// System.out.println("GoogleMap.fromLatLngToPoint bottomLeft: " + bottomLeft);
double scale = Math.pow(2, getZoom());
GMapPoint worldPoint = proj.fromLatLngToPoint(loc);
// System.out.println("GoogleMap.fromLatLngToPoint worldPoint: " + worldPoint);
double x = (worldPoint.getX() - bottomLeft.getX()) * scale;
double y = (worldPoint.getY() - topRight.getY()) * scale;
// System.out.println("GoogleMap.fromLatLngToPoint x: " + x + " y: " + y);
return new Point2D(x, y);
}
/**
* Registers an event handler in the repository shared between Javascript
* and Java.
*
* @param h Event handler to be registered.
* @return Callback key that Javascript will use to find this handler.
*/
private String registerEventHandler(GFXEventHandler h) {
//checkInitialized();
if (!registeredOnJS) {
JSObject doc = (JSObject) runtime.execute("document");
doc.setMember("jsHandlers", jsHandlers);
registeredOnJS = true;
}
return jsHandlers.registerHandler(h);
}
/**
* Adds a handler for a mouse type event on the map.
*
* @param type Type of the event to register against.
* @param h Handler that will be called when the event occurs.
*/
public void addUIEventHandler(UIEventType type, UIEventHandler h) {
this.addUIEventHandler(this, type, h);
}
/**
* Adds a handler for a mouse type event on the map.
*
* @param obj The object that the event should be registered on.
* @param type Type of the event to register against.
* @param h Handler that will be called when the event occurs.
*/
public void addUIEventHandler(JavascriptObject obj, UIEventType type, UIEventHandler h) {
addUIHandler(obj, type, h);
}
/**
* Adds a handler for a mouse type event and returns an object that does not require interaction with
* the underlying Javascript API.
*
* @param type The type of event to listen for
* @param h The MouseEventHandler that will handle the event.
*/
public void addMouseEventHandler(UIEventType type, MouseEventHandler h) {
addUIHandler(this, type, h);
}
protected void addUIHandler(JavascriptObject obj, UIEventType type, GFXEventHandler h) {
String key = registerEventHandler(h);
String mcall = "google.maps.event.addListener(" + obj.getVariableName() + ", '" + type.name() + "', "
+ "function(event) {document.jsHandlers.handleUIEvent('" + key + "', event);});";//.latLng
//System.out.println("addUIEventHandler mcall: " + mcall);
runtime.execute(mcall);
}
/**
* Adds a handler for a state type event on the map.
*
* We could allow this to handle any state event by adding a parameter
* JavascriptObject obj, but we would then need to loosen up the event type
* and either accept a String value, or fill an enum with all potential
* state events.
*
* @param type Type of the event to register against.
* @param h Handler that will be called when the event occurs.
*/
public void addStateEventHandler(MapStateEventType type, StateEventHandler h) {
String key = registerEventHandler(h);
String mcall = "google.maps.event.addListener(" + getVariableName() + ", '" + type.name() + "', "
+ "function() {document.jsHandlers.handleStateEvent('" + key + "');});";
//System.out.println("addStateEventHandler mcall: " + mcall);
runtime.execute(mcall);
}
}