/*
*
* ==============================================================================
* 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 org.wicketstuff.openlayers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.apache.wicket.resource.CoreLibrariesContributor;
import org.wicketstuff.openlayers.api.Bounds;
import org.wicketstuff.openlayers.api.IJavascriptComponent;
import org.wicketstuff.openlayers.api.LonLat;
import org.wicketstuff.openlayers.api.Marker;
import org.wicketstuff.openlayers.api.Overlay;
import org.wicketstuff.openlayers.api.feature.Feature;
import org.wicketstuff.openlayers.api.feature.FeatureStyle;
import org.wicketstuff.openlayers.api.layer.Layer;
import org.wicketstuff.openlayers.api.layer.Vector;
import org.wicketstuff.openlayers.event.EventType;
import org.wicketstuff.openlayers.event.OverlayListenerBehavior;
/**
*
* @author Marin Mandradjiev (marinsm@hotmail.com)
*
*/
public class AjaxOpenLayersMap extends WebMarkupContainer implements IOpenLayersMap
{
private static final long serialVersionUID = 159201381315392564L;
private List<Layer> layers;
private final HashMap<String, String> options;
private final List<Feature> features;
private final List<IJavascriptComponent> controls = new ArrayList<IJavascriptComponent>();
private final List<Overlay> overlays = new ArrayList<Overlay>();
private final List<FeatureStyle> featureStyles;
private final HashMap<String, Vector> featureVectors = new HashMap<String, Vector>();
private boolean externalControls = false;
private Bounds bounds = null;
private LonLat center = null;
private Integer zoom = null;
private String businessLogicProjection = null;
private String markersLayerName = null;
public AjaxOpenLayersMap(final String id, final List<Layer> layers)
{
this(id, layers, null);
}
public AjaxOpenLayersMap(final String id, final List<Layer> layers,
final HashMap<String, String> options)
{
this(id, layers, options, null);
}
public AjaxOpenLayersMap(final String id, final List<Layer> layers,
final HashMap<String, String> options, final List<Feature> features)
{
this(id, layers, options, features, null);
}
public AjaxOpenLayersMap(final String id, final List<Layer> layers,
final HashMap<String, String> options, final List<Feature> features,
final List<FeatureStyle> featureStyles)
{
super(id);
setOutputMarkupId(true);
this.layers = (layers == null) ? new ArrayList<Layer>() : layers;
this.options = (options == null) ? new HashMap<String, String>() : options;
this.features = (features == null) ? new ArrayList<Feature>() : features;
this.featureStyles = (featureStyles == null) ? new ArrayList<FeatureStyle>()
: featureStyles;
add(new Behavior()
{
private static final long serialVersionUID = 1L;
@Override
public void renderHead(Component c, IHeaderResponse response)
{
response.render(OnDomReadyHeaderItem.forScript(getJSinit()));
}
});
for (Feature feature : this.features)
{
getFeatureVector(feature.getDisplayInLayer());
}
}
public static void onPageRenderHead(IHeaderResponse response, String pathToOpenLayersJS)
{
if (pathToOpenLayersJS == null || pathToOpenLayersJS.trim().length() == 0)
{
pathToOpenLayersJS = "http://openlayers.org/api/";
}
else
{
pathToOpenLayersJS = pathToOpenLayersJS.trim();
if (!pathToOpenLayersJS.endsWith("/"))
{
pathToOpenLayersJS = pathToOpenLayersJS + "/";
}
}
pathToOpenLayersJS = pathToOpenLayersJS + "OpenLayers.js";
response.render(JavaScriptHeaderItem.forUrl(pathToOpenLayersJS));
// TODO Import all other JS files which will be used later on
CoreLibrariesContributor.contributeAjax(Application.get(), response);
response.render(JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(AjaxOpenLayersMap.class,
"wicket-openlayersmap.js")));
}
public void setExternalControls(boolean externalControls)
{
this.externalControls = externalControls;
}
public boolean isExternalControls()
{
return externalControls;
}
public void setBounds(Bounds bounds)
{
this.bounds = bounds;
}
public Bounds getBounds()
{
return bounds;
}
public void setCenter(LonLat center)
{
this.center = center;
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(getJSSetCenter());
}
}
public LonLat getCenter()
{
return center;
}
public void setZoom(Integer zoom)
{
this.zoom = zoom;
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(getJSSetCenter());
}
}
public Integer getZoom()
{
return zoom;
}
public void setCenter(LonLat center, Integer zoom)
{
this.center = center;
this.zoom = zoom;
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(getJSSetCenter());
}
}
private String getJSSetCenter()
{
if (center == null)
{
return "";
}
String transformation = "";
if (getBusinessLogicProjection() != null)
{
transformation = ".transform(new OpenLayers.Projection(\"" +
getBusinessLogicProjection() + "\"), " + getJSinvokeNoLineEnd("map") +
".getProjectionObject())";
}
if (zoom == null)
{
return getJSinvoke("setCenter(" + center.getJSconstructor() + transformation + ")");
}
return getJSinvoke("setCenter(" + center.getJSconstructor() + transformation + ", " +
zoom.toString() + ")");
}
private String getJSSetBusinessLogicProjection()
{
if (businessLogicProjection == null)
{
return getJSinvoke("setBusinessLogicProjection(null)");
}
return getJSinvoke("setBusinessLogicProjection('" + businessLogicProjection + "')");
}
/**
* Add a control.
*
* @param control
* control to add
* @return This
*/
public IOpenLayersMap addControl(IJavascriptComponent control)
{
controls.add(control);
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(control.getJSadd(this));
}
return this;
}
/**
* Add an overlay.
*
* @param overlay
* overlay to add
* @return This
*/
public IOpenLayersMap addOverlay(Overlay overlay)
{
overlays.add(overlay);
if (overlay instanceof Marker)
{
((Marker)overlay).setMap(this);
}
for (OverlayListenerBehavior behavior : overlay.getBehaviors())
{
add(behavior);
}
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(getJsOverlay(overlay));
}
return this;
}
/**
* Add a layer.
*
* @param layer
* layer to add
* @return This
*/
public IOpenLayersMap addLayer(Layer layer)
{
layers.add(layer);
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(layer.getJSAddLayer(this));
}
return this;
}
/**
* Add a feature.
*
* @param feature
* feature to add
* @return This
*/
public IOpenLayersMap addFeature(Feature feature)
{
features.add(feature);
feature.setMap(this);
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(
feature.getJSAddFeature(this, getFeatureVector(feature.getDisplayInLayer())));
}
return this;
}
/**
* Add a feature style.
*
* @param featureStyle
* featureStyle to add
* @return This
*/
public IOpenLayersMap addFeatureStyle(FeatureStyle featureStyle)
{
featureStyles.add(featureStyle);
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(featureStyle.getJSAddStyle(this));
}
return this;
}
public IOpenLayersMap removeFeatureStyle(FeatureStyle featureStyle)
{
while (featureStyles.contains(featureStyle))
{
featureStyles.remove(featureStyle);
}
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(featureStyle.getJSRemoveStyle(this));
}
return this;
}
/**
* Clear all overlays.
*
* @return This
*/
public IOpenLayersMap clearOverlays()
{
for (Overlay overlay : overlays)
{
for (OverlayListenerBehavior behavior : overlay.getBehaviors())
{
remove(behavior);
}
}
overlays.clear();
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(getJSinvoke("clearOverlays()"));
}
return this;
}
/**
* Generates the JavaScript used to instantiate this AjaxOpenLayersMap as an JavaScript class on
* the client side.
*
* @return The generated JavaScript
*/
protected String getJSinit()
{
StringBuffer js = new StringBuffer();
String jsMarkersLayerName = getMarkersLayerName() == null ? ", null" : ", '" +
getMarkersLayerName() + "'";
if (options.size() > 0)
{
js.append("\nvar options = {");
boolean first = true;
for (String key : options.keySet())
{
if (first)
{
first = false;
}
else
{
js.append(",\n");
}
js.append(key + ":" + options.get(key));
}
js.append("};\n");
js.append("new WicketOMap('" + this.getMarkupId() + "', options" + jsMarkersLayerName +
", true);\n");
}
else
{
js.append("new WicketOMap('" + this.getMarkupId() + "', null" + jsMarkersLayerName +
", true);\n");
}
for (FeatureStyle featureStyle : featureStyles)
{
js.append(featureStyle.getJSAddStyle(this));
}
// This will add all layers which are used in features
for (Feature feature : features)
{
getFeatureVector(feature.getDisplayInLayer());
}
for (Layer layer : getLayers())
{
js.append(layer.getJSAddLayer(this));
}
for (Feature feature : features)
{
js.append(feature.getJSAddFeature(this, getFeatureVector(feature.getDisplayInLayer())));
}
js.append(getJSSetCenter());
if (center == null || zoom == null)
{
js.append(getJSinvoke("zoomToMaxExtent()"));
}
for (IJavascriptComponent control : controls)
{
js.append(control.getJSadd(this));
}
// Add the overlays.
for (Overlay overlay : overlays)
{
js.append(getJsOverlay(overlay));
}
if (businessLogicProjection != null)
{
js.append(getJSSetBusinessLogicProjection());
}
return js.toString();
}
/**
* Convenience method for generating a JavaScript call on this AjaxOpenLayersMap with the given
* invocation.
*
* @param invocation
* The JavaScript call to invoke on this AjaxOpenLayersMap.
* @return The generated JavaScript.
*/
public String getJSinvoke(String invocation)
{
return "Wicket.omaps['" + this.getMarkupId() + "']." + invocation + ";\n";
}
public String getJSinvokeNoLineEnd(String invocation)
{
return "Wicket.omaps['" + this.getMarkupId() + "']." + invocation;
}
private String getJsOverlay(Overlay overlay)
{
String jsToRun = overlay.getJSadd(this) + "\n";
if (overlay instanceof Marker)
{
Marker marker = (Marker)overlay;
// if marker has popup and there are no events attached then attach
// default listener
if (marker.getPopup() != null &&
(marker.getEvents() == null || marker.getEvents().length == 0))
{
// add mousedown listener!
marker.addEvent(EventType.mousedown);
}
// TODO add listeners
if (marker.getIcon() != null)
{
// prepend icon stuff
jsToRun = marker.getIcon().getSize().getJSadd() +
marker.getIcon().getOffset().getJSadd() + marker.getIcon().getJSadd() + jsToRun;
}
}
return jsToRun;
}
/**
* Remove a control.
*
* @param control
* control to remove
* @return This
*/
public IOpenLayersMap removeControl(IJavascriptComponent control)
{
controls.remove(control);
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(control.getJSremove(this));
}
return this;
}
/**
* Remove an overlay.
*
* @param overlay
* overlay to remove
* @return This
*/
public IOpenLayersMap removeOverlay(Overlay overlay)
{
while (overlays.contains(overlay))
{
overlays.remove(overlay);
}
for (OverlayListenerBehavior behavior : overlay.getBehaviors())
{
remove(behavior);
}
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(overlay.getJSremove(this));
}
return this;
}
/**
* Remove an feature.
*
* @param feature
* feature to remove
* @return This
*/
public IOpenLayersMap removeFeature(Feature feature)
{
while (features.contains(feature))
{
features.remove(feature);
}
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(
feature.getJSRemoveFeature(this, getFeatureVector(feature.getDisplayInLayer())));
}
return this;
}
/**
* Update state from a request to an AJAX target.
*/
public void update(AjaxRequestTarget target)
{
Request request = RequestCycle.get().getRequest();
// Attention: don't use setters as this will result in an endless
// AJAX request loop
center = LonLat.parseWithNames(request.getRequestParameters()
.getParameterValue("centerConverted")
.toString());
zoom = Integer.parseInt(request.getRequestParameters()
.getParameterValue("zoomConverted")
.toString());
bounds = Bounds.parseWithNames(request.getRequestParameters()
.getParameterValue("boundsConverted")
.toString());
}
public List<Layer> getLayers()
{
return layers;
}
public List<IJavascriptComponent> getControls()
{
return controls;
}
public String getJSInstance()
{
return "Wicket.omaps['" + this.getMarkupId() + "']";
}
public List<Overlay> getOverlays()
{
return Collections.unmodifiableList(overlays);
}
public void setLayers(List<Layer> layers)
{
this.layers = layers;
}
public void setOverlays(List<Overlay> overlays)
{
clearOverlays();
for (Overlay overlay : overlays)
{
addOverlay(overlay);
}
}
public void setBusinessLogicProjection(String businessLogicProjection)
{
this.businessLogicProjection = businessLogicProjection;
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(getJSSetBusinessLogicProjection());
}
}
public String getBusinessLogicProjection()
{
return businessLogicProjection;
}
public Vector getFeatureVector(String name)
{
Vector vector = featureVectors.get(name);
if (vector == null)
{
vector = new Vector(name == null ? "Default" : name);
addLayer(vector);
featureVectors.put(name, vector);
}
return vector;
}
public void setMarkersLayerName(String markersLayerName)
{
this.markersLayerName = markersLayerName;
}
public String getMarkersLayerName()
{
return markersLayerName;
}
public void setVisibleOnlyLayers(String... names)
{
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
List<String> invisibleNames = new ArrayList<String>();
invisibleNames.add(markersLayerName);
invisibleNames.addAll(featureVectors.keySet());
StringBuffer visibleLayers = new StringBuffer();
String layerId = null;
for (String name : names)
{
layerId = findLayerId(name);
if (layerId != null)
{
if (visibleLayers.length() > 0)
visibleLayers.append(",");
visibleLayers.append(layerId);
}
invisibleNames.remove(name);
}
StringBuffer invisibleLayers = new StringBuffer();
for (String name : invisibleNames)
{
layerId = findLayerId(name);
if (layerId != null)
{
if (invisibleLayers.length() > 0)
invisibleLayers.append(",");
invisibleLayers.append(layerId);
}
}
ajaxRequestTarget.appendJavaScript(
getJSinvoke("setLayersVisibility([" + visibleLayers.toString() + "], [" +
invisibleLayers.toString() + "])"));
}
}
public void toggleLayer(String name)
{
String layerId = findLayerId(name);
if (layerId != null)
{
AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
if (ajaxRequestTarget != null) {
ajaxRequestTarget.appendJavaScript(getJSinvoke("toggleLayer(" + layerId + ")"));
}
}
}
private String findLayerId(String name)
{
Vector vector = featureVectors.get(name);
if (vector != null)
return vector.getId();
return markersLayerName != null && markersLayerName.equals(name) ? "1" : null;
}
}