/*
* 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;
import co.foldingmap.map.vector.NetworkLayer;
import co.foldingmap.map.vector.LatLonAltBox;
import co.foldingmap.map.vector.VectorLayer;
import co.foldingmap.map.vector.NodeMap;
import co.foldingmap.map.vector.SearchResultsLayer;
import co.foldingmap.map.vector.VectorObject;
import co.foldingmap.map.vector.VectorObjectList;
import co.foldingmap.map.vector.Coordinate;
import co.foldingmap.GUISupport.ProgressIndicator;
import co.foldingmap.GUISupport.Updateable;
import co.foldingmap.actions.Actions;
import co.foldingmap.actions.UpdateObjectOutlines;
import co.foldingmap.map.raster.RasterLayer;
import co.foldingmap.map.themes.MapTheme;
import co.foldingmap.map.themes.MapThemeManager;
import co.foldingmap.map.visualization.TimeSpanControl;
import co.foldingmap.map.visualization.VisualizationLayer;
import co.foldingmap.xml.XmlOutput;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.util.ArrayList;
/**
*
* @author Alec
*/
public class DigitalMap {
public static final String[] DATUM = {"WGS86"};
public static final String[] MAP_PROJECTION = {"Web Mercator"};
private Actions actions;
private ArrayList<Layer> layers;
private ArrayList<Updateable> upateables;
private Coordinate lookAtCoordinate;
private File mapFile;
private Layer selectedLayer;
private long lastObjectReference;
private MapObjectList<MapObject> selectedObjects;
private MapTheme mapTheme;
private MapThemeManager mapThemeManager;
private MapView lastMapView;
private NodeMap coordinateSet;
private SearchResultsLayer searchResultsLayer;
private String mapDescription, mapName, versionNumber;
private TimeSpanControl timeSpanControl;
public DigitalMap() {
init();
this.lastMapView = new MapView(new MercatorProjection());
this.mapName = "New Map";
this.versionNumber = "0.1";
this.lastObjectReference = 0;
this.selectedObjects = new MapObjectList<MapObject>();
this.upateables = new ArrayList<Updateable>();
}
public DigitalMap(String name, MapProjection projection) {
init();
this.coordinateSet = new NodeMap();
this.mapName = name;
this.lastMapView = new MapView(projection);
this.versionNumber = "0.1";
this.lastObjectReference = 0;
this.selectedObjects = new MapObjectList<MapObject>();
this.upateables = new ArrayList<Updateable>();
}
/**
* Adds a Coordinate to this map's NodeMap.
*
* @param c
*/
public void addCoordinateNode(Coordinate c) {
this.coordinateSet.put(c);
}
/**
* Adds a layer to map and returns the index it was added to.
*
* @param newLayer
* @return
*/
public int addLayer(Layer newLayer) {
int layerIndex;
if (selectedLayer == null)
selectedLayer = newLayer;
layers.add(newLayer);
layerIndex = layers.size() - 1;
newLayer.setParentMap(this);
if (newLayer instanceof VisualizationLayer) {
VisualizationLayer vl = (VisualizationLayer) newLayer;
if (vl.hasTimeSeries()) {
timeSpanControl = vl.getTimeSpanControl();
}
}
fireUpdates();
return layerIndex;
}
/**
* Adds a new layer at a given index, or z-order.
*
* @param newLayer
* @param atIndex
*/
public void addLayer(Layer newLayer, int atIndex) {
try {
layers.add(atIndex, newLayer);
newLayer.setParentMap(this);
if (newLayer instanceof VisualizationLayer) {
VisualizationLayer vl = (VisualizationLayer) newLayer;
if (vl.hasTimeSeries())
timeSpanControl = vl.getTimeSpanControl();
}
} catch (Exception e) {
System.err.println("Error in DigitalMap.addLayer(Layer, int) - " + e);
}
fireUpdates();
}
/**
* Adds an Updateable object to this DigitalMap.
*
* @param u
*/
public void addUpdateable(Updateable u) {
this.upateables.add(u);
}
/**
* Calculates the screen points of all Coordinates.
* This is called once every time the map is drawn.
*
* @param mapView
*/
public void calculateCoordinateLocations(MapView mapView) {
float y;
//Convert all the coordinates once, to save cpu
if (coordinateSet != null) {
for (Coordinate c: coordinateSet.getAllCoordinates()) {
if (c != null) {
y = mapView.getY(c);
c.setCenterPoint(mapView.getX(c, MapView.NO_WRAP), y);
if (mapView.getMapProjection().isLeftShown())
c.setLeftPoint(mapView.getX(c, MapView.WRAP_LEFT), y);
if (mapView.getMapProjection().isRightShown())
c.setRightPoint(mapView.getX(c, MapView.WRAP_RIGHT), y);
}
}
}
}
/**
* Returns if this map contains a Raster Layer;
*
* @return
*/
public boolean containsRasterLayer() {
boolean result = false;
for (Layer l: layers) {
if (l instanceof RasterLayer) {
result = true;
break;
}
}
return result;
}
/**
* Cleanly close the map.
*
*/
public void closeMap() {
//TODO: Check to see if map need saving
for (Layer l: layers)
l.closeLayer();
layers.clear();
coordinateSet = null;
actions = null;
System.gc();
}
/**
* Deselects all objects in the map
*/
public void deselectObjects() {
for (MapObject object: this.selectedObjects) {
object.setHighlighted(false);
object.setSelectedCoordinate(Coordinate.UNKNOWN_COORDINATE);
}
selectedObjects.clear();
}
//draws the map by calling the drawObject method of each object in the map
public void drawMap(Graphics2D g2, MapView mapView) {
this.lastMapView = mapView;
mapView.setMapTheme(mapTheme);
mapView.getLabelManager().clear();
calculateCoordinateLocations(mapView);
//draw each layer, in reverse order
for (int l = layers.size() - 1; l >= 0; l--) {
Layer currentLayer = layers.get(l);
currentLayer.drawLayer(g2, mapView);
}
if (searchResultsLayer != null)
searchResultsLayer.drawLayer(g2, mapView);
//draw selected object points
if (mapView.arePointsShown()) {
for (MapObject object: selectedObjects) {
if (object.isHighlighted() && object.getParentLayer().isVisible() == true)
object.drawPoints(g2, mapView);
}
}
//draw labels
mapView.getLabelManager().drawLabels(g2);
}
/**
* Executes the update() method on all the updateable objects added to this DigitalMap.
*/
protected void fireUpdates() {
for (Updateable u: this.upateables)
u.update();
}
/**
* Returns the actions manager used with this map.
*
* @return
*/
public Actions getActions() {
return actions;
}
/**
* Gets all the custom data field names associated with this object.
*
* @return Vector<String> A Vector containing all of the Custom Field Names.
*/
public ArrayList<String> getAllCustomDataFields() {
boolean stringFound;
ArrayList<String> allFields, objectFields;
VectorLayer currentVectorLayer;
allFields = new ArrayList<String>();
for (Layer currentLayer: layers) {
if (currentLayer instanceof VectorLayer) {
currentVectorLayer = (VectorLayer) currentLayer;
objectFields = currentVectorLayer.getObjectList().getAllCustomDataFields();
for (String currentField: objectFields) {
stringFound = false;
for (String currentAllField: allFields) {
if (currentField.equals(currentAllField))
stringFound = true;
}
if (!stringFound)
allFields.add(currentField);
} //end fields for loop
} //end Vector Layer Check
} // end objects for loop
return allFields;
}
/**
* Returns all the MapObjects in each layer of this Map.
*
* @return
*/
public MapObjectList<MapObject> getAllMapObjects() {
MapObjectList<MapObject> objects;
VectorLayer vl;
objects = new MapObjectList<MapObject>();
for (Layer l: layers) {
if (l instanceof VectorLayer) {
vl = (VectorLayer) l;
objects.addAll(vl.getObjectList());
}
}
return objects;
}
/**
* Returns the boundary for this map.
*
* @return
*/
public LatLonAltBox getBoundary() {
LatLonAltBox bounds, layerBounds;
Layer layer0;
layer0 = layers.get(0);
bounds = layer0.getBoundary();
for (Layer l: this.layers) {
layerBounds = l.getBoundary();
bounds = LatLonAltBox.combine(bounds, layerBounds);
}
return bounds;
}
/**
* Returns the maps CoordinateSet, null if it has not been set.
*
* @return
*/
public NodeMap getCoordinateSet() {
return this.coordinateSet;
}
/**
* Returns all the values for a specified custom field name of objects
* in the map.
*
* @param fieldName The field name for the associated value.
* @return ArrayList<String> The value for the passed in fieldName.
*/
public ArrayList<String> getCustomDataFieldValue(String fieldName) {
ArrayList<String> values;
VectorLayer vl;
values = new ArrayList<String>();
for (Layer l: this.layers) {
if (l instanceof VectorLayer) {
vl = (VectorLayer) l;
values.addAll(vl.getCustomDataFieldValue(fieldName));
}
}
return values;
}
/**
* Returns the last MapView used by this map.
*
* @return
*/
public MapView getLastMapView() {
return lastMapView;
}
/**
* Returns the Layer at the given index in this Map's Layer List.
*
* @param layerNumber
* @return
*/
public Layer getLayer(int layerNumber) {
return layers.get(layerNumber);
}
/**
* Returns an ArrayList of all the Layers in this Map.
*
* @return
*/
public ArrayList<Layer> getLayers() {
return layers;
}
/**
* Gets the Coordinate used to set the view port position when
* the map is opened.
*
* @return
*/
public Coordinate getLookAtCoordinate() {
if (lookAtCoordinate == null)
lookAtCoordinate = new Coordinate(0,0,0);
return lookAtCoordinate;
}
/**
* Returns a VectorObject from any layer with the given reference.
* Returns null if the object reference cannot be found.
*
* @param ref
* @return
*/
public VectorObject getMapObjectFromReference(long ref) {
VectorObject object = null;
VectorLayer vl;
for (Layer l: layers) {
if (l instanceof VectorLayer) {
vl = (VectorLayer) l;
object = vl.getMapObjectFromReference(ref);
if (object != null)
break;
}
}
return object;
}
/**
* Returns this Map's description.
*
* @return
*/
public String getMapDescription() {
if (mapDescription == null) {
return "";
} else {
return this.mapDescription;
}
}
/**
* Returns the file holding this map's data.
*
* @return The file this map is saved to.
*/
public File getMapFile() {
return mapFile;
}
/**
* Returns the theme manager for this map.
*
* @return
*/
public MapThemeManager getMapThemeManager() {
return mapThemeManager;
}
/**
* Returns the maximum value for a numeric data field of all objects in this map.
*
* @param fieldName The field name to find the maximum.
* @return double The maximum value for the supplied field.
*/
public double getMaximumFieldValue(String fieldName) {
double fieldValue, valueMax;
VectorLayer currentVectorLayer;
valueMax = Double.MIN_VALUE;
for (Layer currentLayer: layers) {
if (currentLayer instanceof VectorLayer) {
currentVectorLayer = (VectorLayer) currentLayer;
fieldValue = currentVectorLayer.getMaximumFieldValue(fieldName);
if (fieldValue > valueMax)
valueMax = fieldValue;
}
}
return valueMax;
}
/**
* Returns the minimum value for a numeric data field of all objects in this map.
*
* @param fieldName The field name to find the minimum.
* @return double The minimum value for the supplied field.
*/
public double getMinimumFieldValue(String fieldName) {
double fieldValue, valueMin;
VectorLayer currentVectorLayer;
valueMin = Double.MAX_VALUE;
for (Layer currentLayer: layers) {
if (currentLayer instanceof VectorLayer) {
currentVectorLayer = (VectorLayer) currentLayer;
fieldValue = currentVectorLayer.getMinimumFieldValue(fieldName);
if (fieldValue < valueMin)
valueMin = fieldValue;
}
}
return valueMin;
}
/**
* Returns this Map's name.
*
* @return
*/
public String getName() {
return mapName;
}
/**
* Returns a new reference to use with an object. This will be unique for
* the whole map;
*
* @return
*/
public long getNewObjectReference() {
return lastObjectReference++;
}
/**
* Returns the MapOBject closes to the given Coordinate.
*
* @param c
* @return
*/
public VectorObject getObjectClosestTo(Coordinate c) {
VectorObject closetsObject;
VectorObjectList<VectorObject> closestInEachLayer;
VectorLayer currentVectorLayer;
closestInEachLayer = new VectorObjectList<VectorObject>();
try {
//get the closest in each layer
for (Layer currentLayer: layers) {
if (currentLayer instanceof VectorLayer) {
currentVectorLayer = (VectorLayer) currentLayer;
closetsObject = currentVectorLayer.getObjectList().getObjectClosestTo(c);
closestInEachLayer.add(closetsObject);
} //end vector layer check
} //end layers loop
} catch (Exception e) {
System.err.println("Error in DigitalMap.getObjectClosestTo.(Coordinate) - " + e);
}
return closestInEachLayer.getObjectClosestTo(c);
}
/**
* Returns the currently selected Layer.
*
* @return
*/
public Layer getSelectedLayer() {
if (selectedLayer == null)
selectedLayer = this.getVectorLayer();
return selectedLayer;
}
/**
* Returns the Selected objects from all layers.
*
* @return
*/
public MapObjectList<MapObject> getSelectedObjects() {
return selectedObjects;
}
/**
* Returns the Theme being used by this map.
*
* @return
*/
public MapTheme getTheme() {
if (mapTheme == null)
mapTheme = new MapTheme("Default Theme");
return mapTheme;
}
/**
* Returns the TimeSpanControl, if it exists, otherwise returns null.
*
* @return
*/
public TimeSpanControl getTimeSpanControl() {
return timeSpanControl;
}
/**
* Returns the first VectorLayer in this map.
* If there are no VectorLayers a new one is added.
*
* @return
*/
public VectorLayer getVectorLayer() {
VectorLayer vectorLayer = null;
for (Layer l: this.getLayers()) {
if (l instanceof NetworkLayer) {
} else if (l instanceof VectorLayer) {
vectorLayer = (VectorLayer) l;
break;
}
}
if (vectorLayer == null) {
vectorLayer = new VectorLayer("New Layer");
this.addLayer(selectedLayer);
}
return vectorLayer;
}
/**
* Returns the version number for this map as a String.
*
* @return
*/
public String getVersionNumber() {
return versionNumber;
}
/**
* Highlights objects within a given range.
*
* @param range
* @param controlPressed
*/
public void highlightObject(Rectangle2D range, boolean controlPressed) {
try {
MapObjectList<MapObject> newlySelectedObjects;
newlySelectedObjects = (selectObjects(range));
if (controlPressed) {
//Handle the control select
//If objects are already selected unselect them.
selectedObjects = getSelectedObjects();
for (int i = 0; i < newlySelectedObjects.size(); i++) {
MapObject currentObject = newlySelectedObjects.get(i);
if (selectedObjects.contains(currentObject)) {
selectedObjects.remove(currentObject);
currentObject.setHighlighted(false);
} else {
selectedObjects.add(currentObject);
currentObject.setHighlighted(true);
}
}
} else {
deselectObjects();
selectedObjects = newlySelectedObjects;
for (int i = 0; i < selectedObjects.size(); i++) {
MapObject currentObject = selectedObjects.get(i);
currentObject.setHighlighted(true);
}
}//End if controlPressed
setSelected(selectedObjects);
} catch (Exception e) {
System.err.println("Error in DigitalMap.highlightObject.(Rectangle2D, boolean) " + e);
}
}
/**
* Initialize object for this digital map.
*/
private void init() {
coordinateSet = new NodeMap();
layers = new ArrayList<Layer>();
mapThemeManager = new MapThemeManager();
mapTheme = new MapTheme("Default Theme");
}
/**
* Removes all layers from this map.
*/
public void removeAllLayers() {
this.layers.clear();
fireUpdates();
}
/**
* Removes a given layer from the map.
*
* @param l
*/
public void removeLayer(Layer l) {
this.layers.remove(l);
if (l instanceof VisualizationLayer) {
VisualizationLayer vl = (VisualizationLayer) l;
if (vl.hasTimeSeries()) {
timeSpanControl = null;
}
}
fireUpdates();
}
/**
* Selects all objects in the given range and returns those selected
* MapObjects in a list.
*
* @param range
* @return
*/
public MapObjectList<MapObject> selectObjects(Rectangle2D range) {
MapObjectList<MapObject> selectedObjectsFromLayer, selectedObjects;
selectedObjects = new MapObjectList<MapObject>();
//if there is a serch in progress try to select the results first
if (searchResultsLayer != null) {
selectedObjectsFromLayer = this.searchResultsLayer.selectObjects(range);
if (selectedObjects.isEmpty()) {
selectedObjects.addAll(selectedObjectsFromLayer);
} else if ((range.getWidth() != MapPanel.SINGLE_CLICK_WIDTH)) {
selectedObjects.addAll(selectedObjectsFromLayer);
}
}
for (Layer currentLayer: layers) {
if (currentLayer.isVisible() && !currentLayer.isLocked()) {
selectedObjectsFromLayer = currentLayer.selectObjects(range);
if (selectedObjects.isEmpty()) {
selectedObjects.addAll(selectedObjectsFromLayer);
} else if ((range.getWidth() != MapPanel.SINGLE_CLICK_WIDTH)) {
//Check to see if we are doing a mass select or not
selectedObjects.addAll(selectedObjectsFromLayer);
}
}//end visible and locked check
}
return selectedObjects;
}
/**
* Sets the Action manager to be used with this map.
*
* @param actions The actions manager to be used with this map.
*/
public void setActions(Actions actions) {
this.actions = actions;
}
/**
* Sets the CoordinateSet for this map
*
* @param coordinateSet
*/
public void setCoordinateSet(NodeMap coordinateSet) {
this.coordinateSet = coordinateSet;
}
/**
* Sets the description for this map
* @param mapDescription
*/
public void setMapDescription(String mapDescription) {
this.mapDescription = mapDescription;
}
/**
* Sets the LookAtCoordinate for this map.
* The LookAtCoordinate is the part of the map displayed when it is loaded.
*
* @param c
*/
public void setLookAtCoordinate(Coordinate c) {
this.lookAtCoordinate = c;
this.lastMapView.getMapProjection().setReference(c);
}
/**
* Sets the file this map was loaded from.
*
* @param mapFile
*/
public void setMapFile(File mapFile) {
this.mapFile = mapFile;
}
/**
* Sets the name for this Map.
*
* @param newName
*/
public void setName(String newName) {
this.mapName = newName;
}
/**
* Sets the node map to be used by this map.
*
* @param newNodeMap
*/
public void setNodeMap(NodeMap newNodeMap) {
this.coordinateSet = newNodeMap;
}
/**
* Selects the object given.
*
* @param objectToSelect
*/
public void setSelected(MapObject objectToSelect) {
objectToSelect.setHighlighted(true);
selectedObjects.add(objectToSelect);
}
/**
* Highlights and adds the provided list of MapObjects to the selected objects.
*
* @param objectsToSelect
*/
public void setSelected(MapObjectList<MapObject> objectsToSelect) {
for (MapObject object: objectsToSelect)
setSelected(object);
}
/**
* Highlights and adds the provided list of VectorObjects to the selected objects.
*
* @param objectsToSelect
*/
public void setSelected(VectorObjectList<VectorObject> objectsToSelect) {
for (VectorObject object: objectsToSelect)
setSelected(object);
}
/**
* Sets the selected layer for this map.
*
* @param newSelectedLayer
*/
public void setSelectedLayer(Layer newSelectedLayer) {
selectedLayer = newSelectedLayer;
}
/**
* Sets the theme associated with this map.
*
* @param mapTheme
* @param updateable
* @param progressIndicator The indicator used to show progress for this method.
*/
public void setTheme(MapTheme mapTheme,
Updateable updateable,
ProgressIndicator progressIndicator) {
this.mapTheme = mapTheme;
UpdateObjectOutlines uoo = new UpdateObjectOutlines(mapTheme, this.layers, updateable, progressIndicator);
uoo.start();
}
public void toXML(XmlOutput kmlWriter) {
double mapLatitude, mapLongitude, mapZoomLevel;
try {
mapLatitude = lastMapView.getMapProjection().getReferenceLatitude() ;
mapLongitude = lastMapView.getMapProjection().getReferenceLongitude() ;
mapZoomLevel = lastMapView.getZoomLevel();
kmlWriter.writeTextLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
kmlWriter.openTag("kml xmlns=\"http://www.opengis.net/kml/2.2\"");
kmlWriter.openTag("Document");
kmlWriter.writeTag("name", mapName);
kmlWriter.writeTag("open", "1");
kmlWriter.openTag ("LookAt");
kmlWriter.writeTag("longitude", Double.toString(mapLongitude));
kmlWriter.writeTag("latitude", Double.toString(mapLatitude));
kmlWriter.writeTag("gx:Zoomlevel", Double.toString(mapZoomLevel));
//kmlWriter.writeTag("altitude", Double.toString(mapZoomLevel)); //removed until it can be made compatible
kmlWriter.closeTag("LookAt");
mapTheme.toXML(kmlWriter);
//layer objects
for (Layer currentLayer: layers) {
currentLayer.toXML(kmlWriter);
}
kmlWriter.closeTag("Document");
kmlWriter.closeTag("kml");
} catch (Exception e) {
System.err.println("Error in DigitalMap.toXML(KmlWritter) - " + e);
}
}
}