/**
* Copyright (c) 2011-2014, OpenIoT
*
* This file is part of OpenIoT.
*
* OpenIoT is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* OpenIoT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with OpenIoT. If not, see <http://www.gnu.org/licenses/>.
*
* Contact: OpenIoT mailto: info@openiot.eu
*/
package org.openiot.ui.request.presentation.web.model.nodes.impl;
import java.util.List;
import javax.el.MethodExpression;
import javax.faces.application.Application;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import org.openiot.commons.osdspec.model.PresentationAttr;
import org.openiot.commons.sdum.serviceresultset.model.SdumServiceResultSet;
import org.openiot.commons.sparql.protocoltypes.model.QueryResult;
import org.openiot.commons.sparql.result.model.Binding;
import org.openiot.commons.sparql.result.model.Result;
import org.openiot.ui.request.presentation.web.model.nodes.interfaces.VisualizationWidget;
import org.openiot.ui.request.presentation.web.util.FaceletLocalization;
import org.primefaces.component.behavior.ajax.AjaxBehavior;
import org.primefaces.component.behavior.ajax.AjaxBehaviorListenerImpl;
import org.primefaces.component.commandlink.CommandLink;
import org.primefaces.component.gmap.GMapInfoWindow;
import org.primefaces.component.outputpanel.OutputPanel;
import org.primefaces.component.panel.Panel;
import org.primefaces.context.RequestContext;
import org.primefaces.event.map.OverlaySelectEvent;
import org.primefaces.event.map.StateChangeEvent;
import org.primefaces.model.map.Circle;
import org.primefaces.model.map.DefaultMapModel;
import org.primefaces.model.map.LatLng;
import org.primefaces.model.map.Marker;
public class Map implements VisualizationWidget {
private enum MapType {
Markers, Circles, MarkersAndCircles
};
private org.primefaces.component.gmap.GMap widget;
private HtmlOutputText infoWindowContent;
private Panel panel;
private DefaultMapModel model;
private String title;
private Double centerLat;
private Double centerLon;
private Integer zoom;
private Double valueScaler;
private MapType mapType;
// Map state
private Marker selectedMarker;
private String mapViewType;
private String mapCenter;
private int mapZoom;
@Override
public Panel createWidget(String serviceId, List<PresentationAttr> presentationAttributes) {
FacesContext fc = FacesContext.getCurrentInstance();
Application application = fc.getApplication();
parseAttributes(presentationAttributes);
// Setup initial map state
mapViewType = mapType.toString();
mapCenter = centerLat + "," + centerLon;
mapZoom = zoom;
// Instanciate map widget
model = new DefaultMapModel();
widget = (org.primefaces.component.gmap.GMap) application.createComponent(fc, "org.primefaces.component.GMap", "org.primefaces.component.GMapRenderer");
widget.setId("map_" + System.nanoTime());
widget.setWidgetVar(widget.getId());
widget.setStyleClass("map");
widget.setValueExpression("type", application.getExpressionFactory().createValueExpression(fc.getELContext(), "#{requestPresentationPageController.context.serviceIdToWidgetMap[\"" + serviceId + "\"].mapViewType}", String.class));
widget.setValueExpression("center", application.getExpressionFactory().createValueExpression(fc.getELContext(), "#{requestPresentationPageController.context.serviceIdToWidgetMap[\"" + serviceId + "\"].mapCenter}", String.class));
widget.setValueExpression("zoom", application.getExpressionFactory().createValueExpression(fc.getELContext(), "#{requestPresentationPageController.context.serviceIdToWidgetMap[\"" + serviceId + "\"].mapZoom}", Integer.class));
widget.setRendered(true);
widget.setMapTypeControl(true);
widget.setModel(model);
// Add ajax behavior so we can maintain zoom and pan between map updates
MethodExpression dummyExpression = application.getExpressionFactory().createMethodExpression(fc.getELContext(), "#{requestPresentationPageController.dummy}", void.class, new Class[] {});
MethodExpression stateChangeMethodExpression = application.getExpressionFactory().createMethodExpression(fc.getELContext(), "#{requestPresentationPageController.context.serviceIdToWidgetMap[\"" + serviceId + "\"].onMapWidgetStateChange}", void.class, new Class[] { StateChangeEvent.class });
AjaxBehavior ajaxBehavior = new AjaxBehavior();
ajaxBehavior.setProcess("@this");
ajaxBehavior.addAjaxBehaviorListener(new AjaxBehaviorListenerImpl(dummyExpression, stateChangeMethodExpression));
widget.addClientBehavior("stateChange", ajaxBehavior);
// Add ajax behavior so we can track selected markers
MethodExpression markerSelectMethodExpression = application.getExpressionFactory().createMethodExpression(fc.getELContext(), "#{requestPresentationPageController.context.serviceIdToWidgetMap[\"" + serviceId + "\"].onMapWidgetMarkerSelection}", void.class, new Class[] { OverlaySelectEvent.class });
ajaxBehavior = new AjaxBehavior();
ajaxBehavior.setProcess("@this");
ajaxBehavior.addAjaxBehaviorListener(new AjaxBehaviorListenerImpl(dummyExpression, markerSelectMethodExpression));
widget.addClientBehavior("overlaySelect", ajaxBehavior);
// Instanciate infowindow widget and embed it in map widget
GMapInfoWindow infoWindow = new GMapInfoWindow();
infoWindow.setId("map_infowindow_" + System.nanoTime());
OutputPanel infoWindowPanel = new OutputPanel();
infoWindowPanel.setStyleClass("text-align:center;display:block;margin:auto;");
infoWindowContent = new HtmlOutputText();
infoWindowContent.setEscape(false);
infoWindowPanel.getChildren().add(infoWindowContent);
infoWindow.getChildren().add(infoWindowPanel);
widget.getChildren().add(infoWindow);
// Instanciate a panel to host the widget
panel = (Panel) application.createComponent(fc, "org.primefaces.component.Panel", "org.primefaces.component.PanelRenderer");
panel.setId("widget_panel_" + System.nanoTime());
panel.setHeader(title != null ? title : "");
panel.setClosable(false);
panel.setToggleable(false);
panel.setStyleClass("widget service_" + serviceId);
panel.getChildren().add(widget);
// Generate clear data link
CommandLink clearLink = (CommandLink) application.createComponent(fc, "org.primefaces.component.CommandLink", "org.primefaces.component.CommandLinkRenderer");
clearLink.setAjax(true);
clearLink.setOnclick("windowBlockUI.block();");
clearLink.setOncomplete("windowBlockUI.unblock();");
clearLink.setProcess("@this");
clearLink.setStyleClass("ui-panel-titlebar-icon ui-corner-all ui-state-default");
HtmlOutputText btn = new HtmlOutputText();
btn.setStyleClass("ui-icon ui-icon-arrowrefresh-1-w");
clearLink.getChildren().add(btn);
clearLink.addActionListener(new ActionListener() {
@Override
public void processAction(ActionEvent arg0) throws AbortProcessingException {
clearData();
}
});
panel.getFacets().put("actions", clearLink);
return panel;
}
@Override
public void processData(SdumServiceResultSet resultSet) {
boolean triggerUpdate = true;
model.getMarkers().clear();
model.getCircles().clear();
for (QueryResult resultBlock : resultSet.getQueryResult()) {
for (Result result : resultBlock.getSparql().getResults().getResult()) {
// Parse data
Double lat = null;
Double lon = null;
Double value = null;
for (Binding binding : result.getBinding()) {
if ("VALUE".equals(binding.getName())) {
value = Double.valueOf(binding.getLiteral().getContent());
} else if ("LAT".equals(binding.getName())) {
lat = Double.valueOf(binding.getLiteral().getContent());
} else if ("LON".equals(binding.getName())) {
lon = Double.valueOf(binding.getLiteral().getContent());
}
}
// Update series
if (value != null && lat != null && lon != null) {
String overlayData = FaceletLocalization.getLocalisedMessage(FaceletLocalization.getLocalizedResourceBundle(), "UI_MAP_WIDGET_MARKER_TEMPLATE", "(" + lat + ", " + lon + ")", value);
LatLng coord = new LatLng(lat, lon);
if (MapType.Markers.equals(mapType)) {
model.addOverlay(new Marker(coord, "sensor", overlayData));
} else if (MapType.Circles.equals(mapType)) {
Circle circle = new Circle(coord, value * valueScaler);
circle.setStrokeColor("#ff0000");
circle.setStrokeOpacity(0.8);
circle.setFillColor("#ff0000");
circle.setFillOpacity(0.35);
model.addOverlay(circle);
} else {
Circle circle = new Circle(coord, value * valueScaler);
circle.setStrokeColor("#ff0000");
circle.setStrokeOpacity(0.8);
circle.setFillColor("#ff0000");
circle.setFillOpacity(0.35);
model.addOverlay(new Marker(coord, "sensor", overlayData));
model.addOverlay(circle);
}
}
}
}
if (triggerUpdate) {
// If we update the map, we will trigger a reload (causes flicker).
// Instead, we will use the JS api to remove all overlays and then
// add the new ones.
RequestContext requestContext = RequestContext.getCurrentInstance();
if (requestContext != null) {
requestContext.execute("clearMapOverlays(" + widget.getWidgetVar() + ");");
for (Circle circle : widget.getModel().getCircles()) {
requestContext.execute("addMapCircle(" + widget.getWidgetVar() + ", '" + circle.getId() + "', " + circle.getCenter().getLat() + "," + circle.getCenter().getLng() + ", " + circle.getRadius() + ",'" + circle.getStrokeColor() + "'," + circle.getStrokeOpacity() + ",'" + circle.getFillColor() + "', " + circle.getFillOpacity() + ");");
}
for (Marker marker : widget.getModel().getMarkers()) {
requestContext.execute("addMapMarker(" + widget.getWidgetVar() + ", '" + marker.getId() + "', " + marker.getLatlng().getLat() + "," + marker.getLatlng().getLng() + ");");
}
requestContext.execute("addMapEventListeners(" + widget.getWidgetVar() + ");");
}
}
}
private void parseAttributes(List<PresentationAttr> presentationAttributes) {
for (PresentationAttr attr : presentationAttributes) {
if ("TITLE".equals(attr.getName())) {
title = attr.getValue();
} else if ("LAT".equals(attr.getName())) {
centerLat = Double.valueOf(attr.getValue());
} else if ("LON".equals(attr.getName())) {
centerLon = Double.valueOf(attr.getValue());
} else if ("ZOOM".equals(attr.getName())) {
zoom = Integer.valueOf(attr.getValue());
zoom = Math.max(0, Math.min(zoom, 19));
} else if ("SCALER".equals(attr.getName())) {
valueScaler = Double.valueOf(attr.getValue());
if (valueScaler == 0) {
valueScaler = 1.0;
}
} else if ("TYPE".equals(attr.getName())) {
String type = attr.getValue();
if ("Circles only".equals(type)) {
mapType = MapType.Circles;
} else if ("Markers and Circles".equals(type)) {
mapType = MapType.MarkersAndCircles;
} else {
mapType = MapType.Markers;
}
}
}
}
@Override
public void clearData() {
model.getMarkers().clear();
model.getCircles().clear();
selectedMarker = null;
infoWindowContent.setValue("");
// Reset map state to original values
mapViewType = mapType.toString();
mapCenter = centerLat + "," + centerLon;
mapZoom = zoom;
RequestContext requestContext = RequestContext.getCurrentInstance();
if (requestContext != null) {
requestContext.update(panel.getClientId());
}
}
// ------------------------------------------------------
// Ajax behavior listener for monitoring map location
// ------------------------------------------------------
public void onMapWidgetStateChange(StateChangeEvent event) {
mapZoom = event.getZoomLevel();
mapCenter = event.getCenter().getLat() + ", " + event.getCenter().getLng();
}
public void onMapWidgetMarkerSelection(OverlaySelectEvent event) {
selectedMarker = (Marker) event.getOverlay();
infoWindowContent.setValue(selectedMarker.getData() != null ? selectedMarker.getData() : "");
}
public String getMapViewType() {
return mapViewType;
}
public void setMapViewType(String mapViewType) {
this.mapViewType = mapViewType;
}
public String getMapCenter() {
return mapCenter;
}
public void setMapCenter(String mapCenter) {
this.mapCenter = mapCenter;
}
public int getMapZoom() {
return mapZoom;
}
public void setMapZoom(int mapZoom) {
this.mapZoom = mapZoom;
}
public Marker getSelectedMarker() {
return selectedMarker;
}
public void setSelectedMarker(Marker selectedMarker) {
this.selectedMarker = selectedMarker;
}
}