/*
* Copyright (C) 2014 Alec Dhuse
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package co.foldingmap.map.visualization;
import co.foldingmap.map.MapView;
import co.foldingmap.map.MapObjectList;
import co.foldingmap.map.MapObject;
import co.foldingmap.map.MapPanel;
import co.foldingmap.map.Layer;
import co.foldingmap.map.vector.MultiGeometry;
import co.foldingmap.map.vector.LatLonAltBox;
import co.foldingmap.map.vector.VectorObjectList;
import co.foldingmap.map.vector.VectorObject;
import co.foldingmap.map.vector.MapPoint;
import co.foldingmap.map.vector.LineString;
import co.foldingmap.map.vector.Polygon;
import co.foldingmap.map.themes.PolygonStyle;
import co.foldingmap.map.themes.ColorRamp;
import co.foldingmap.map.themes.ColorHelper;
import co.foldingmap.map.themes.ColorStyle;
import co.foldingmap.map.themes.IconStyle;
import co.foldingmap.map.themes.LineStyle;
import co.foldingmap.Logger;
import co.foldingmap.xml.XmlOutput;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.JMenuItem;
/**
*
* @author Alec
*/
public class HeatMap extends VisualizationLayer {
protected ArrayList<String> objectRefs;
protected HeatMapKey heatMapKey;
protected int position;
protected VectorObjectList<VectorObject> objects;
protected MapView lastMapView;
protected String colorRampID;
protected String[] variables;
/**
* Constructor for the HeatMap Layer.
*
* @param layerName
* The name of the layer.
*
* @param objects
* The List of Object to be used when creating the HeatMap, these
* should be present in other layers.
*
* @param hashMap
* The HashMap containing the value to color mappings used in this
* HeatMap.
*
* @param variables
* An array of variables used to create this HeatMap. Multiple
* variables indicates a Time Series. A single variable indicates
* a static HeatMap.
*
* @param displayInterval
* The time in milliseconds to display each HeatMap in the time
* series before transitioning to the next one.
*/
public HeatMap(String layerName,
VectorObjectList<VectorObject> objects,
String colorRampID,
String[] variables,
int displayInterval,
HeatMapKey heatMapKey) {
this.displayInterval = displayInterval;
this.colorRampID = colorRampID;
this.objects = objects;
this.variables = variables;
this.layerName = layerName;
this.visible = true;
this.position = 0;
this.heatMapKey = heatMapKey;
if (heatMapKey.getPositionReference() == HeatMapKey.NONE) {
this.showKey = false;
} else {
this.showKey = true;
}
if (this.hasTimeSeries())
timeControl = new TimeSpanControl(this);
}
/**
* Constructor for the HeatMap Layer.
*
* @param layerName
* The name of the layer.
*
* @param objects
* The List of Object to be used when creating the HeatMap, these
* should be present in other layers.
*
* @param hashMap
* The HashMap containing the value to color mappings used in this
* HeatMap.
*
* @param variables
* An array of variables used to create this HeatMap. Multiple
* variables indicates a Time Series. A single variable indicates
* a static HeatMap.
*
* @param displayInterval
* The time in milliseconds to display each HeatMap in the time
* series before transitioning to the next one.
*/
public HeatMap(String layerName,
ArrayList<String> objectRefs,
String colorRampID,
String[] variables,
int displayInterval,
HeatMapKey heatMapKey) {
this.displayInterval = displayInterval;
this.colorRampID = colorRampID;
this.objectRefs = objectRefs;
this.objects = null;
this.variables = variables;
this.layerName = layerName;
this.visible = true;
this.position = 0;
this.heatMapKey = heatMapKey;
if (heatMapKey.getPositionReference() == HeatMapKey.NONE) {
this.showKey = false;
} else {
this.showKey = true;
}
if (this.hasTimeSeries())
timeControl = new TimeSpanControl(this);
}
/**
* Closes this Layer gracefully.
*/
@Override
public void closeLayer() {
}
@Override
public Layer copy() {
return new HeatMap(layerName, objects, colorRampID, variables, displayInterval, heatMapKey);
}
@Override
public void drawLayer(Graphics2D g2, MapView mapView) {
Color color;
ColorRamp colorRamp;
ColorStyle newStyle;
int time;
LineStyle lineStyle;
String id, value;
try {
colorRamp = mapView.getMapTheme().getColorRamp(colorRampID);
if (objects == null) {
//objects not loaded load from object refs
objects = new VectorObjectList<VectorObject>();
for (String ref: objectRefs) {
VectorObject obj = parentMap.getMapObjectFromReference(Long.parseLong(ref));
objects.add(obj);
}
}
if (this.hasTimeSeries()) {
time = timeControl.getPosition();
} else {
time = 0;
}
lastMapView = mapView;
if (visible) {
for (VectorObject object: objects) {
value = object.getCustomDataFieldValue(variables[time]);
color = colorRamp.getColor(value);
newStyle = null;
if (object instanceof MapPoint) {
id = "HeatMapPoint-" + value;
newStyle = new IconStyle(id, color);
} else if (object instanceof LineString) {
id = "HeatMapLine-" + value;
lineStyle = (LineStyle) mapView.getMapTheme().getLineStyle(object.getObjectClass());
newStyle = new LineStyle(id, color, lineStyle.getOutlineColor(), lineStyle.getLineWidth(), lineStyle.getLineStroke());
} else if (object instanceof Polygon) {
id = "HeatMapPoly-" + value;
newStyle = new PolygonStyle(id, color, color);
} else if (object instanceof MultiGeometry) {
}
if (newStyle != null && value != null)
object.drawObject(g2, mapView, newStyle);
}
if (showKey) {
//Draw HeatMap key
heatMapKey.drawObject(g2, mapView);
}
}//end visibility check
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in HeatMap.drawLayer(Graphics2D, MapView) - " + e);
}
}
@Override
public LatLonAltBox getBoundary() {
return objects.getBoundary();
}
@Override
public float getCenterLongitude() {
float center, delta;
delta = objects.getEasternMostLongitude() - objects.getWesternMostLongitude();
center = objects.getWesternMostLongitude() + (float) (delta / 2.0);
return center;
}
@Override
public float getCenterLatitude() {
float center, delta;
delta = objects.getNorthernMostLatitude() - objects.getSouthernMostLatitude();
center = objects.getSouthernMostLatitude() + (float) (delta / 2.0);
return center;
}
/**
* Returns the hashMap holding the value to color mappings.
*
* @return
*/
public String getColorRampID() {
return colorRampID;
}
/**
* Returns JMenuItems that should be used in the context menu for this Layer
*
* @return
*/
@Override
public JMenuItem[] getContextMenuItems() {
return new JMenuItem[0];
}
/**
* Returns HeatMapKey for this HeatMap
*
* @return
*/
public HeatMapKey getHeatMapKey() {
return heatMapKey;
}
/**
* Returns the MapObjects used to create the HeatMap
*
* @return
*/
public VectorObjectList<VectorObject> getMapObjects() {
return objects;
}
/**
* Returns the number of elements in the variables string array. This will
* determine how many positions there are in the time series.
*
* @return
*/
@Override
public int getNumberOfSeries() {
return variables.length;
}
/**
* Returns the VectorObject variables used to create this heatMap.
*
* @return
*/
public String[] getVariables() {
return variables;
}
@Override
public final boolean hasTimeSeries() {
if (variables.length > 1) {
return true;
} else {
return false;
}
}
@Override
public MapObjectList selectObjects(Rectangle2D range) {
VectorObject currentObject;
MapObjectList<MapObject> newlySelectedObjects;
newlySelectedObjects = new MapObjectList<MapObject>();
try {
//search backwards to get objects with higher z-oreder first
for (int i = (objects.size() - 1); i >= 0; i--) {
currentObject = objects.get(i);
if (currentObject.isObjectWithinRectangle(range)) {
if (currentObject.isVisible(lastMapView))
newlySelectedObjects.add(currentObject);
//code for a single mouse click
//selection only returns the object closes to the top
if ((range.getWidth() == MapPanel.SINGLE_CLICK_WIDTH) && (newlySelectedObjects.size() > 0))
break;
}
}
} catch (Exception e) {
System.err.println("Error in HeatMap.selectObjects(Rectangle2D) - " + e);
}
return (newlySelectedObjects);
}
/**
* Sets the MapObjects to be used in this HeatMap.
*
* @param objects
*/
public void setMapObjects(VectorObjectList<VectorObject> objects) {
this.objects = objects;
}
/**
* Sets the HashMap to be used by this HeatMap.
*
* @param hashMap
*/
public void setColorRampID(String colorRampID) {
this.colorRampID = colorRampID;
}
/**
* Sets the variables to be used by this HeatMap.
*
* @param variables
*/
public void setVariables(String[] variables) {
this.variables = variables;
}
@Override
public void toXML(XmlOutput kmlWriter) {
StringBuilder fields, objectRefs;
objectRefs = new StringBuilder();
fields = new StringBuilder();
kmlWriter.openTag("heatmap");
kmlWriter.writeTag("name", layerName);
kmlWriter.writeTag("description", layerDescription);
//variables
for (String f: variables) {
fields.append(f);
fields.append(",");
}
kmlWriter.openTag("key");
if (heatMapKey.getPositionReference() == HeatMapKey.NONE) {
kmlWriter.writeTag("Position", "None");
} else if (heatMapKey.getPositionReference() == HeatMapKey.BOTTOM_LEFT) {
kmlWriter.writeTag("Position", "Bottom-Left");
} else if (heatMapKey.getPositionReference() == HeatMapKey.BOTTOM_RIGHT) {
kmlWriter.writeTag("Position", "Bottom-Right");
} else if (heatMapKey.getPositionReference() == HeatMapKey.TOP_LEFT) {
kmlWriter.writeTag("Position", "Top-Left");
} else if (heatMapKey.getPositionReference() == HeatMapKey.TOP_RIGHT) {
kmlWriter.writeTag("Position", "Top-Right");
}
if (heatMapKey.hasHorizontalOrientation()) {
kmlWriter.writeTag("Orientation", "Horizontal");
} else {
kmlWriter.writeTag("Orientation", "Vertical");
}
for (Color c: heatMapKey.getColors())
kmlWriter.writeTag("color", ColorHelper.getColorHexStandard(c));
kmlWriter.closeTag("key");
fields.deleteCharAt(fields.lastIndexOf(","));
kmlWriter.writeTag("fields", fields.toString());
//colorRamp
kmlWriter.writeTag("Style", colorRampID);
//objects
for (VectorObject object: objects) {
objectRefs.append(object.getReference());
objectRefs.append(",");
}
objectRefs.deleteCharAt(objectRefs.lastIndexOf(","));
kmlWriter.writeTag("objects", objectRefs.toString());
kmlWriter.closeTag("heatmap");
}
}