/* * 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.vector; import co.foldingmap.map.Overlay; 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.Logger; import co.foldingmap.map.raster.ImageOverlay; import co.foldingmap.xml.XmlOutput; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import javax.swing.JMenuItem; /** * A Layer that holds VectorObject, which are vector objects. * * @author Alec */ public class VectorLayer extends Layer { protected ArrayList<Overlay> overlays; protected VectorObjectList<VectorObject> objects; protected MapView lastMapView; protected SimpleDateFormat timestampDateFormat; /** * Constructor for objects of class VectorLayer */ public VectorLayer() { this.layerName = "New Vector Layer"; this.visible = true; this.objects = new VectorObjectList<VectorObject>(); this.layerPropertiesPanel = new VecorLayerPropertiesPanel(this); } /** * Constructor for objects of class VectorLayer * @param layerName */ public VectorLayer(String layerName) { this.layerName = layerName; this.visible = true; this.objects = new VectorObjectList<VectorObject>(); //this.selectedObjects = new VectorObjectList<MapObject>(); this.layerPropertiesPanel = new VecorLayerPropertiesPanel(this); } /** * Adds a list of map objects to this layer. * * @param objectsToAdd */ public void addAllObjects(VectorObjectList<VectorObject> objectsToAdd) { objects.addAll(objectsToAdd); for (int i = 0; i < objectsToAdd.size(); i++) { VectorObject currentMapObject = objectsToAdd.get(i); currentMapObject.setParentLayer(this); if (this.parentMap.getTheme() != null) currentMapObject.updateOutlines(this.parentMap.getTheme()); //Updates NodeMap for the map for (Coordinate c: currentMapObject.getCoordinateList()) this.parentMap.addCoordinateNode(c); //Check to see if the object has a reference, if not give it one. if (currentMapObject.getReference() < 1) { if (parentMap != null) { currentMapObject.setReference(parentMap.getNewObjectReference()); } else { System.err.println("Parent Map for layer: " + layerName + " was not set before adding new objects. No object references will be set"); } } } } /** * Adds a VectorObject to this layer at a specific z-position. * * @param obj * @param posistion */ public void addObject(VectorObject obj, int posistion) { objects.add(posistion, obj); obj.setParentLayer(this); obj.updateOutlines(this.parentMap.getTheme()); //Updates NodeMap for the map for (Coordinate c: obj.getCoordinateList()) this.parentMap.addCoordinateNode(c); //Check to see if the object has a reference, if not give it one. if (obj.getReference() < 1) { if (parentMap != null) { obj.setReference(parentMap.getNewObjectReference()); } else { System.err.println("Parent Map for layer: " + layerName + " was not set before adding new objects. No object references will be set"); } } } /** * Adds a VectorObject to this Layer. * * @param obj */ public void addObject(VectorObject obj) { try { objects.add(obj); obj.setParentLayer(this); if (parentMap != null) { obj.updateOutlines(this.parentMap.getTheme()); if (parentMap != null) obj.setReference(parentMap.getNewObjectReference()); } else { Logger.log(Logger.ERR, "Parent Map for layer: " + layerName + " was not set before adding new objects. No object references will be set"); } } catch (Exception e) { Logger.log(Logger.ERR, "Error in VectorLayer.addObject(VectorObject) - " + e); } } /** * Adds an Overlay to this layer. * * @param overlay */ public void addOverlay(Overlay overlay) { if (overlay != null) { //see if the arraylist has been initialied or not. if (overlays == null) overlays = new ArrayList<Overlay>(); overlays.add(overlay); if (overlay instanceof ImageOverlay) ((ImageOverlay) overlay).setParentLayer(this); } } /** * Adds one or more Overlays to this layer. * * @param overlays */ public void addOverlays(ArrayList<Overlay> overlays) { for (Overlay ol: overlays) addOverlay(ol); } /** * Removes all Overlays from this VectorLayer. * */ public void clearOverlays() { if (this.overlays != null) this.overlays.clear(); } /** * Closes this Layer gracefully. */ @Override public void closeLayer() { objects = null; overlays = null; } @Override public Layer copy() { VectorObject currentMapObjectCopy; VectorObjectList<VectorObject> objectsCopy; VectorLayer layerCopy; objectsCopy = new VectorObjectList<VectorObject>(); //create a copy of every object for (int i = 0; i < objects.size(); i++) { VectorObject currentMapObject = objects.get(i); currentMapObjectCopy = (VectorObject) currentMapObject.copy(); objectsCopy.add(currentMapObjectCopy); } layerCopy = new VectorLayer(layerName); layerCopy.setParentMap(parentMap); layerCopy.addAllObjects(objectsCopy); return layerCopy; } /** * Draws this Layer on the map. * * @param g2 * @param mapView */ @Override public void drawLayer(Graphics2D g2, MapView mapView) { LatLonAltBox objectBounds, viewBounds; LineString currentLineString; VectorObject currentMapObject; VectorObjectList<VectorObject> lineStrings, polygons; Polygon currentPolygon; try { this.lastMapView = mapView; viewBounds = new LatLonAltBox(mapView.getViewBounds()); if (visible) { for (int i = 0; i < objects.size(); i++) { currentMapObject = objects.get(i); if (currentMapObject instanceof Polygon) { currentMapObject.drawObject(g2, mapView, null); currentMapObject.drawOutline(g2, mapView, false); } else if (currentMapObject instanceof LineString) { currentMapObject.drawOutline(g2, mapView, false); } else if (currentMapObject instanceof MultiGeometry) { currentMapObject.drawOutline(g2, mapView, false); } } //draw all the other objects for (int i = 0; i < objects.size(); i++) { currentMapObject = objects.get(i); if (currentMapObject != null) { objectBounds = currentMapObject.getBoundingBox(); float latEast = mapView.getLongitude(mapView.getDisplayWidth(), 0); float diff = Math.abs(mapView.getLongitude(0, 0)) + latEast; if (mapView.displayAll()) { currentMapObject.drawObject(g2, mapView, null); if (currentMapObject instanceof Polygon) { ((Polygon) currentMapObject).drawOutline(g2, mapView, false); } else if (currentMapObject instanceof LineString) { } } else { if (viewBounds.overlaps(objectBounds) || diff >= 90) { if (currentMapObject instanceof Polygon) { // if (!mapView.isDragging() && !currentMapObject.isHighlighted()) { // currentMapObject.drawOutline(g2, mapView); // } } else { currentMapObject.drawObject(g2, mapView, null); } } } } else { //end null check //clean up nulls objects.remove(i); } } //draw overlays if (overlays != null) { for (Overlay overlay: overlays) { overlay.drawObject(g2, mapView); } } }// if visible } catch (Exception e) { System.err.println("Error in VectorLayer.drawLayer(Graphics2D, MapView) - " + e); } } /** * Returns all the Coordinates in MapObjects in this Layer. * @return */ public CoordinateList<Coordinate> getAllLayerCoordinates() { CoordinateList<Coordinate> allCoordinates = new CoordinateList<Coordinate>(); try { for (int i = 0; i < objects.size(); i++) { VectorObject currentMapObject = objects.get(i); allCoordinates.addAll(currentMapObject.getCoordinateList()); } } catch (Exception e) { System.err.println("Error VectorLayer.getAllMapCoordinates() " + e); } return allCoordinates; } /** * Returns all Objects within a given range. * * @param range * @return */ public VectorObjectList<VectorObject> getAllObjectsWithinRange(LatLonAltBox range) { return objects.getAllWithinRange(range); } /** * Returns the boundary that contains all the object in this layer. * * @return */ @Override public LatLonAltBox getBoundary() { return this.objects.getBoundary(); } /** * Returns the center Longitude of all object in this Layer. * * @return */ @Override public float getCenterLongitude() { float center, delta; delta = objects.getEasternMostLongitude() - objects.getWesternMostLongitude(); center = objects.getWesternMostLongitude() + (float) (delta / 2.0); return center; } /** * Gets the center Latitude of all the Objects in this Layer. * * @return */ @Override public float getCenterLatitude() { float center, delta; delta = objects.getNorthernMostLatitude() - objects.getSouthernMostLatitude(); center = objects.getSouthernMostLatitude() + (float) (delta / 2.0); return center; } /** * Returns JMenuItems that should be used in the context menu for this Layer * * @return */ @Override public JMenuItem[] getContextMenuItems() { return new JMenuItem[0]; } /** * Returns all the values for a specified custom field name of objects * in this layer. * * @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) { return objects.getCustomDataFieldValue(fieldName); } /** * Returns the EarliestDate used by any Coordinate in this Layer. * * @return */ public Date getEarliestDate() { Date current, earliest; VectorObject currentObject; earliest = new Date(); for (int i = 0; i < objects.size(); i++) { currentObject = objects.get(i); current = new Date(currentObject.getCoordinateList().getEarliestDate()); if (current.before(earliest)) earliest = current; } return earliest; } /** * Gets the Latest Date used by any Coordinate in this Layer. * * @return */ public Date getLatestDate() { Date current, latest; VectorObject currentObject; latest = new Date(objects.get(0).getCoordinateList().getEarliestDate()); for (int i = 0; i < objects.size(); i++) { currentObject = objects.get(i); current = new Date(currentObject.getCoordinateList().getEarliestDate()); if (current.after(latest)) latest = current; } return latest; } /** * Returns a VectorObject from with the given reference. * Returns null if the object reference cannot be found. * * @param ref * @return */ public VectorObject getMapObjectFromReference(long ref) { VectorObject object = null; for (VectorObject obj: objects) { if (ref == obj.getReference()) { object = obj; break; } } return object; } /** * Returns the maximum value for a numeric data field of all object in this layer. * * @param fieldName The field name to find the maximum. * @return double The maximum value for the supplied field. */ public double getMaximumFieldValue(String fieldName) { double valueMax; valueMax = getMaximumFieldValue(fieldName, objects); return valueMax; } /** * Returns the maximum value for a numeric data field of all object in this layer. * * @param String The field name to find the maximum. * @param MapObjectCollection The Vector of MapObjects to Search. * @return double The maximum value for the supplied field. */ public static double getMaximumFieldValue(String fieldName, VectorObjectList<VectorObject> mapObjectsToSearch) { double fieldValue, valueMax; String fieldStringValue; valueMax = Double.MIN_VALUE; for (int i = 0; i < mapObjectsToSearch.size(); i++) { VectorObject currentObject = mapObjectsToSearch.get(i); fieldStringValue = currentObject.getCustomDataFieldValue(fieldName); try { if (fieldStringValue != null) { fieldValue = Double.parseDouble(fieldStringValue); try { if (fieldValue > valueMax) valueMax = fieldValue; } catch (NumberFormatException nfe) { //not a numeric value }//end catch } //end value null check } catch (Exception e) { } }//end for loop return valueMax; } /** * Returns the minimum value for a numeric data field of all object in this layer. * * @param fieldName The field name to find the minimum. * @return double The minimum value for the supplied field. */ public double getMinimumFieldValue(String fieldName) { double valueMin; valueMin = getMinimumFieldValue(fieldName, objects); return valueMin; } /** * Returns the minimum value for a numeric data field of all object in this layer. * * @param String The field name to find the minimum. * @param Vector<MapObject> The Vector of MapObjects to Search. * @return double The minimum value for the supplied field. */ public static double getMinimumFieldValue(String fieldName, VectorObjectList<VectorObject> mapObjectsToSearch) { double fieldValue, valueMin; String fieldStringValue; valueMin = Double.MAX_VALUE; for (VectorObject currentObject: mapObjectsToSearch) { fieldStringValue = currentObject.getCustomDataFieldValue(fieldName); try { if (fieldStringValue != null) { try { fieldValue = Double.parseDouble(currentObject.getCustomDataFieldValue(fieldName)); if (fieldValue < valueMin) valueMin = fieldValue; } catch (NumberFormatException nfe) { //not a numeric value }//end catch } //end if (fieldStringValue != null) } catch (Exception e) { } } //end for loop return valueMin; } /** * Returns a list of all the objects in this layer. * * @return */ public VectorObjectList<VectorObject> getObjectList() { return objects; } /** * Returns any overlays used in this VectorLayer. * * @return */ public ArrayList<Overlay> getOverlays() { if (this.overlays != null) { return this.overlays; } else { return new ArrayList<Overlay>(); } } /** * Returns the Time Span Begin as a String in the format: yyyy-MM-dd'T'HH:mm:ss * * @return */ public String getTimeSpanBeginString() { this.timestampDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); if (timeSpanBegin == null) { return null; } else { return timestampDateFormat.format(timeSpanBegin) + "Z"; } } /** * Returns the Time Span End as a String in the format: yyyy-MM-dd'T'HH:mm:ss * * @return */ public String getTimeSpanEndString() { this.timestampDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); if (timeSpanEnd == null) { return null; } else { return timestampDateFormat.format(timeSpanEnd) + "Z"; } } /** * Removes a given mapObject from the layer. It does not change the * parentLayer field of that object. * * @param object Object to remove. */ public void removeObject(VectorObject object) { objects.remove(object); } /** * Selects all object within the given rectangle, unless user clicks with * a single mouse click. The single mouse click has a specific selection * rectangle with hight and width of 8. * * @param range * @return The collection of objects selected given range */ @Override public MapObjectList<MapObject> selectObjects(Rectangle2D range) { VectorObject currentObject; MapObjectList<MapObject> newlySelectedObjects; newlySelectedObjects = new MapObjectList<MapObject>(); try { //search backwards to get objects with higher z-order first for (int i = (objects.size() - 1); i >= 0; i--) { currentObject = (VectorObject) objects.get(i); if (currentObject != null) { 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; } } else {//end null check //clean up any nulls objects.remove(i); } } } catch (Exception e) { System.err.println("Error in VectorLayer.selectObjects(Rectangle2D) - " + e); } return (newlySelectedObjects); } // end selectObjects /** * Sets the list of objects in this Layer, all previous objects will be * removed. * * @param objects */ public void setObjectList(VectorObjectList<VectorObject> objects) { for (VectorObject object: objects) object.setParentLayer(this); this.objects = objects; //this.selectedObjects.clear(); } /** * Changes a specific VectorObject's Z order to the one specified * * @param VectorObject mapObject, the object to change the Z order of. * @param int zOrder, the new Z position for the object. * @return boolean if the object was found and moved. */ public boolean setObjectZOrder(VectorObject mapObject, int zOrder) { boolean success; success = objects.remove(mapObject); if (success) { if (zOrder > (objects.size() - 1)) { objects.add(mapObject); } else { objects.add(zOrder, mapObject); } } return success; } /** * Writes this Layer as FmXml. * * @param kmlWriter */ @Override public void toXML(XmlOutput kmlWriter) { try { kmlWriter.openTag ("VectorLayer"); kmlWriter.writeTag("name", layerName); if (layerDescription != null) { if (!layerDescription.equals("") && !layerDescription.equalsIgnoreCase("null")) kmlWriter.writeTag("description", layerDescription); } if (hasTimeSpan()) { kmlWriter.openTag ("TimeSpan"); kmlWriter.writeTag("begin", getTimeSpanBeginString()); kmlWriter.writeTag("end", getTimeSpanEndString()); kmlWriter.closeTag("TimeSpan"); kmlWriter.writeTag("locked", Boolean.toString(isLocked())); } kmlWriter.openTag("objects"); //get xml for each object for (int i = 0; i < objects.size(); i++) objects.get(i).toXML(kmlWriter); kmlWriter.closeTag("objects"); kmlWriter.closeTag("VectorLayer"); } catch (Exception e) { System.err.println("Error in VectorLayer.toXML(XmlWriter) - " + e); } } }