/* * 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.mapImportExport.FmXmlImporter; import co.foldingmap.mapImportExport.GeoRssImporter; import co.foldingmap.mapImportExport.KmlImport; import co.foldingmap.mapImportExport.JsonImporter; import co.foldingmap.mapImportExport.CsvImporter; import co.foldingmap.mapImportExport.MapImporter; import co.foldingmap.mapImportExport.JsImporter; import co.foldingmap.map.Overlay; import co.foldingmap.map.MapView; import co.foldingmap.map.MapObjectList; import co.foldingmap.map.MapObject; import co.foldingmap.map.DigitalMap; import co.foldingmap.map.Visibility; import co.foldingmap.map.Layer; import co.foldingmap.Logger; import co.foldingmap.ResourceHelper; import co.foldingmap.map.themes.ColorStyle; import co.foldingmap.map.tile.TileMath; import co.foldingmap.xml.XmlOutput; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Rectangle2D; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.util.ArrayList; import java.util.Date; import javax.swing.JMenuItem; /** * * @author Alec */ public class NetworkLayer extends VectorLayer implements ActionListener { public static final int NEVER = 0; public static final int ON_CHANGE = 1; public static final int ON_INTERVAL = 2; public static final int ON_EXPIRE = 3; protected ArrayList<Layer> layers; protected float refreshInterval; //in seconds protected int refreshMode; protected LatLonBox bounds; protected long lastUpdate; protected JMenuItem menuItemRefresh; protected NodeMap nodeMap; protected ResourceHelper helper; protected String address, defaultPointClass; protected VectorLayer defaultLayer; protected Visibility layerVisibility; public NetworkLayer(String layerName, String address) { this.defaultPointClass = "Point"; this.helper = ResourceHelper.getInstance(); this.lastUpdate = 0; this.layerName = layerName; this.layers = new ArrayList<Layer>(); this.address = address; this.nodeMap = new NodeMap(); this.refreshInterval = 300; this.layerVisibility = new Visibility(); } @Override public void actionPerformed(ActionEvent ae) { if (ae.getSource() == menuItemRefresh) { updateData(); } } /** * Adds a list of map objects to this layer. * * @param objectsToAdd */ @Override public void addAllObjects(VectorObjectList<VectorObject> objectsToAdd) { getDefaultLayer().addAllObjects(objectsToAdd); Logger.log(Logger.WARN, "Objects added to a NetworkLayer will be deleted the next time the layer is reloaded."); } /** * Adds a VectorObject to this Layer. * * @param obj */ @Override public void addObject(VectorObject obj) { getDefaultLayer().addObject(obj); Logger.log(Logger.WARN, "Objects added to a NetworkLayer will be deleted the next time the layer is reloaded."); } /** * Adds a VectorObject to this layer at a specific z-position. * * @param obj * @param posistion */ @Override public void addObject(VectorObject obj, int posistion) { getDefaultLayer().addObject(obj, posistion); Logger.log(Logger.WARN, "Objects added to a NetworkLayer will be deleted the next time the layer is reloaded."); } /** * Adds an Overlay to this layer. * * @param overlay */ @Override public void addOverlay(Overlay overlay) { getDefaultLayer().addOverlay(overlay); } /** * Adds one or more Overlays to this layer. * * @param overlays */ @Override public void addOverlays(ArrayList<Overlay> overlays) { getDefaultLayer().addAllObjects(objects); } /** * Calculates the screen points of all Coordinates in this Layer. * This is called once every time the Layer is drawn. * * @param mapView */ public void calculateCoordinateLocations(MapView mapView) { float y; //Convert all the coordinates once, to save cpu if (nodeMap != null) { for (Coordinate c: nodeMap.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); } } } } /** * Removes all Overlays from this VectorLayer. * */ @Override public void clearOverlays() { //TODO: Make this work for every layer. getDefaultLayer().clearOverlays(); } /** * Closes this Layer gracefully. */ @Override public void closeLayer() { } @Override public Layer copy() { NetworkLayer layerCopy = new NetworkLayer(this.layerName, this.address); layerCopy.setDefaultPointClass(defaultPointClass); layerCopy.setLayerDescription(layerDescription); layerCopy.setLocked(locked); layerCopy.setParentMap(parentMap); layerCopy.setRefreshInterval(refreshInterval); layerCopy.setRefreshMode(refreshMode); layerCopy.setVisibility(layerVisibility.getMinTileZoomLevel(), layerVisibility.getMaxTileZoomLevel()); return layerCopy; } /** * Draws this Layer on the map. * * @param g2 * @param mapView */ @Override public void drawLayer(Graphics2D g2, MapView mapView) { boolean drawLayer; try { calculateCoordinateLocations(mapView); //See if this layer is visible and if we need to draw this layer. if (this.visible == true) { drawLayer = layerVisibility.isVisible(mapView.getZoomLevel()); if (drawLayer && (bounds != null)) { drawLayer = mapView.getViewBounds().overlaps(bounds); } } else { drawLayer = false; } if (drawLayer) { //check to see if the map needs updating if ((lastUpdate + (refreshInterval * 1000)) < System.currentTimeMillis()) updateData(); for (int i = layers.size() - 1; i >= 0; i--) { Layer l = this.layers.get(i); l.drawLayer(g2, mapView); } } } catch (Exception e) { System.err.println("Error in NetworkLayer.drawLayer(Graphics2D, MapView) - " + e); } } @Override public boolean equals(Object obj) { if (obj instanceof NetworkLayer) { return (this.hashCode() == obj.hashCode()); } else { return false; } } @Override public int hashCode() { int hash = 7; hash = 53 * hash + Float.floatToIntBits(this.refreshInterval); hash = 53 * hash + this.refreshMode; hash = 53 * hash + (this.address != null ? this.address.hashCode() : 0); hash = 53 * hash + (this.layerName != null ? this.layerName.hashCode() : 0); hash = 53 * hash + (this.layerDescription != null ? this.layerDescription.hashCode() : 0); hash = 53 * hash + (this.layerVisibility != null ? this.layerVisibility.hashCode() : 0); return hash; } /** * Returns the address that this NetworkLayer references. * * @return */ public String getAddress() { return address; } /** * Returns all the Coordinates in MapObjects in this Layer. * @return */ @Override public CoordinateList<Coordinate> getAllLayerCoordinates() { CoordinateList<Coordinate> coords = new CoordinateList<Coordinate>(); for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; coords.addAll(vl.getAllLayerCoordinates()); } } return coords; } /** * Returns all Objects within a given range. * * @param range * @return */ @Override public VectorObjectList<VectorObject> getAllObjectsWithinRange(LatLonAltBox range) { VectorObjectList<VectorObject> objectsInRange = new VectorObjectList<VectorObject>(); for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; objectsInRange.addAll(vl.getAllObjectsWithinRange(range)); } } return objectsInRange; } /** * Returns the boundary that contains all the object in this layer. * * @return */ @Override public LatLonAltBox getBoundary() { LatLonAltBox boundary = null; for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; if (boundary == null) { boundary = vl.getBoundary(); } else { boundary = LatLonAltBox.combine(boundary, vl.getBoundary()); } } } return getDefaultLayer().getBoundary(); } /** * Returns the center Longitude of all object in this Layer. * * @return */ @Override public float getCenterLongitude() { return (float) getBoundary().getCenter().getLongitude(); } /** * Gets the center Latitude of all the Objects in this Layer. * * @return */ @Override public float getCenterLatitude() { return (float) getBoundary().getCenter().getLatitude(); } /** * Returns JMenuItems that should be used in the context menu for this Layer * * @return */ @Override public JMenuItem[] getContextMenuItems() { JMenuItem[] menuItems; menuItems = new JMenuItem[1]; menuItemRefresh = new JMenuItem("Refresh"); menuItems[0] = menuItemRefresh; menuItemRefresh.addActionListener(this); return menuItems; } /** * 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. */ @Override public ArrayList<String> getCustomDataFieldValue(String fieldName) { ArrayList<String> values = new ArrayList<String>(); for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; values.addAll(vl.getCustomDataFieldValue(fieldName)); } } return values; } /** * Method to get the default layer. * This method creates the default layer if it does not exists. * * @return */ private VectorLayer getDefaultLayer() { if (defaultLayer == null) { this.defaultLayer = new VectorLayer("Default Layer"); this.defaultLayer.setParentMap(parentMap); this.layers.add(defaultLayer); } return defaultLayer; } /** * Returns the EarliestDate used by any Coordinate in this Layer. * * @return */ @Override public Date getEarliestDate() { Date returnDate = null; for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; if (returnDate == null) { returnDate = vl.getEarliestDate(); } else { if (returnDate.after(vl.getEarliestDate())) returnDate = vl.getEarliestDate(); } } } return returnDate; } /** * Gets the Latest Date used by any Coordinate in this Layer. * * @return */ @Override public Date getLatestDate() { Date returnDate = null; for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; if (returnDate == null) { returnDate = vl.getLatestDate(); } else { if (returnDate.before(vl.getLatestDate())) returnDate = vl.getLatestDate(); } } } return returnDate; } /** * Returns a VectorObject from with the given reference. * Returns null if the object reference cannot be found. * * @param ref * @return */ @Override public VectorObject getMapObjectFromReference(long ref) { VectorObject returnObject = null; for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; returnObject = vl.getMapObjectFromReference(ref); if (returnObject != null) break; } } return returnObject; } /** * 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. */ @Override public double getMaximumFieldValue(String fieldName) { double currentVal, maxVal = Double.MIN_VALUE; for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; currentVal = vl.getMaximumFieldValue(fieldName); if (currentVal > maxVal) maxVal = currentVal; } } return maxVal; } /** * 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. */ @Override public double getMinimumFieldValue(String fieldName) { double currentVal, minVal = Double.MAX_VALUE; for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; currentVal = vl.getMinimumFieldValue(fieldName); if (currentVal < minVal) minVal = currentVal; } } return minVal; } /** * Returns a list of all the objects in this layer. * * @return */ @Override public VectorObjectList<VectorObject> getObjectList() { VectorObjectList<VectorObject> objList = new VectorObjectList<VectorObject>(); for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; objList.addAll(vl.getObjectList()); } } return objList; } /** * Returns any overlays used in this VectorLayer. * * @return */ @Override public ArrayList<Overlay> getOverlays() { ArrayList<Overlay> overlays = new ArrayList<Overlay>(); for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; overlays.addAll(vl.getOverlays()); } } return overlays; } /** * Returns the Refresh Interval for this Network Layer. * * @return */ public float getRefreshInterval() { return refreshInterval; } /** * Creates a Region object with this layer's bounds and a LOD based off of the the Visibility. * @return */ public Region getRegion() { LatLonAltBox bounds = this.getBoundary(); float minLOD = Region.calculateLodFromTileZoom(layerVisibility.getMinTileZoomLevel(), bounds); float maxLOD = Region.calculateLodFromTileZoom(layerVisibility.getMaxTileZoomLevel(), bounds); String regionName = this.getName() + " Region"; return new Region(regionName, bounds, maxLOD, minLOD); } /** * Returns the Time Span Begin as a String in the format: yyyy-MM-dd'T'HH:mm:ss * * @return */ @Override public String getTimeSpanBeginString() { //TODO: Make this work for all layers return super.getTimeSpanBeginString(); } /** * Returns the Time Span End as a String in the format: yyyy-MM-dd'T'HH:mm:ss * * @return */ @Override public String getTimeSpanEndString() { //TODO: Make this work for all layers return super.getTimeSpanEndString(); } /** * Returns the Visibility object used for this NetworkLayer. * * @return */ public Visibility getVisibility() { return this.layerVisibility; } /** * Imports a FmXML file into this NetworkLayer * * @param file */ private void importFmXML(File file) { try { this.bounds = FmXmlImporter.getBounds(file); DigitalMap tempMap = FmXmlImporter.openFile(file, null); if (nodeMap.isEmpty()) { nodeMap = (tempMap.getCoordinateSet()); } else { //copy coordinates into the parent map. for (Coordinate c: tempMap.getCoordinateSet().getAllCoordinates()) { if (c != null) { c.setId(0); //Wipe node ID this.nodeMap.put(c); } } } //Copy all layers into this NetworkLayer. for (Layer l: tempMap.getLayers()) this.layers.add(l); //Copy Styles over for (ColorStyle cs: tempMap.getTheme().getAllStyles()) this.parentMap.getTheme().addStyleElement(cs); } catch (Exception e) { Logger.log(Logger.ERR, "Error in NetworkLayer.importFmXML(File) - " + e); } } /** * Imports a JavaScript file into this NetworkLayer. * * @param jsFile */ private void importJS(File jsFile) { try { String[] vars = JsImporter.getVariableText(ResourceHelper.getTextFromFile(jsFile)); String tmpStr = vars[0]; File tmpFile = File.createTempFile("jsTemp", "js"); FileWriter fw = new FileWriter(tmpFile); jsFile.deleteOnExit(); BufferedWriter bw = new BufferedWriter(fw); bw.write(tmpStr); importJSON(jsFile); } catch (Exception e) { Logger.log(Logger.ERR, "Error in NetworkLayer.importJS(File) - " + e); } } /** * Imports GeoJSON into this Network Layer. * * @param jsonFile */ private void importJSON(File jsonFile) { defaultLayer = new VectorLayer("Json Layer"); defaultLayer.setParentMap(parentMap); layers.add(defaultLayer); JsonImporter json = new JsonImporter(); MapImporter importer = new MapImporter(json, jsonFile, nodeMap, this, null); importer.start(); } /** * Removes a given mapObject from the layer. It does not change the * parentLayer field of that object. * * @param object The object to remove. Only removes it from this instance, * not from the linked file. */ @Override public void removeObject(VectorObject object) { for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; vl.removeObject(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) { MapObjectList<MapObject> objectList = new MapObjectList<MapObject>(); for (Layer l: this.layers) objectList.addAll(l.selectObjects(range)); return objectList; } public void setAddress(String address) { this.address = address; } /** * Sets the Default class for newly created MapPoints. * The default is Point. * * @param newClass */ public void setDefaultPointClass(String newClass) { this.defaultPointClass = newClass; } /** * Sets the list of objects in this Layer, all previous objects will be * removed. * * @param objects */ @Override public void setObjectList(VectorObjectList<VectorObject> objects) { getDefaultLayer().selectObjects(null); } /** * Changes a specific VectorObject's Z order to the one specified * * @param mapObject mapObject, the object to change the Z order of. * @param zOrder zOrder, the new Z position for the object. * @return boolean if the object was found and moved. */ @Override public boolean setObjectZOrder(VectorObject mapObject, int zOrder) { for (Layer l: this.layers) { if (l instanceof VectorLayer) { VectorLayer vl = (VectorLayer) l; if (vl.objects.contains(mapObject)) { vl.setObjectZOrder(mapObject, zOrder); break; } } } return getDefaultLayer().setObjectZOrder(mapObject, zOrder); } /** * Sets the interval, in seconds that this layer should be refreshed. * * @param refreshInterval */ public void setRefreshInterval(float refreshInterval) { this.refreshInterval = refreshInterval; } /** * Sets the refresh mode for this NetworkLayer with an int. * See static integer for this class for values. * * @param refreshMode */ public void setRefreshMode(int refreshMode) { this.refreshMode = refreshMode; } /** * Sets the refresh mode for this NetworkLayer with a String. * Possible values are: never, onChange, onExpire, onInterval. * * @param refreshMode */ public void setRefreshMode(String refreshMode) { if (refreshMode.equalsIgnoreCase("never")) { this.refreshMode = NetworkLayer.NEVER; } else if (refreshMode.equalsIgnoreCase("onChange")) { this.refreshMode = NetworkLayer.ON_CHANGE; } else if (refreshMode.equalsIgnoreCase("onExpire")) { this.refreshMode = NetworkLayer.ON_EXPIRE; } else if (refreshMode.equalsIgnoreCase("onInterval")) { this.refreshMode = NetworkLayer.ON_INTERVAL; } } /** * Takes a Region with a defined LevelOfDetail and uses it to set the visibility values for * this network layer. * * @param region */ public void setRegion(Region region) { if (region.getLevelOfDetail() != null) { //Convert region to Vector Zoom Levels LevelOfDetail regionLOD = region.getLevelOfDetail(); double minVectorZoom = region.calculateVectorZoomLevelForLOD(regionLOD.getMinLodPixels()); double maxVectorZoom = region.calculateVectorZoomLevelForLOD(regionLOD.getMaxLodPixels()); //Set the visibility values layerVisibility.setMinTileZoomLevel(TileMath.getTileMapZoom((float) minVectorZoom)); layerVisibility.setMaxTileZoomLevel(TileMath.getTileMapZoom((float) maxVectorZoom)); } else { Logger.log(Logger.ERR, "Error in NetworkLayer.setRegion(Region) - Supplied Region does not contain a LevelOfDetail."); } } /** * Sets the minimum and maximum visibility for this NetworkLayer. * * @param minZoom * @param maxZoom */ public void setVisibility(float minZoom, float maxZoom) { layerVisibility.setMinTileZoomLevel(minZoom); layerVisibility.setMaxTileZoomLevel(maxZoom); } public void updateData() { File tempFile; int fileNameStart; lastUpdate = System.currentTimeMillis(); try { if (!address.equals("")) { tempFile = ResourceHelper.downloadFile(address); if (tempFile != null) { if (tempFile.getName().endsWith("csv") || address.endsWith("csv")) { this.getObjectList().clear(); //Remove all current objects CsvImporter csv = new CsvImporter(tempFile, this.parentMap, this); csv.setDefaultPointClass(defaultPointClass); csv.start(); } else if (tempFile.getName().endsWith("fmxml")) { importFmXML(tempFile); } else if (tempFile.getName().endsWith("geojson")) { importJSON(tempFile); } else if (tempFile.getName().endsWith("js")) { //javascript importJS(tempFile); } else if (tempFile.getName().endsWith("kml")) { KmlImport kmlImporter = new KmlImport(); MapImporter importer = new MapImporter(kmlImporter, tempFile, nodeMap, this, null); importer.start(); } else if (tempFile.getName().endsWith("kmz")) { fileNameStart = address.lastIndexOf("/"); if (fileNameStart >= 0) ResourceHelper.addFilePath(address.substring(0, fileNameStart + 1)); KmlImport kmlImporter = new KmlImport(); MapImporter importer = new MapImporter(kmlImporter, tempFile, nodeMap, this, null); importer.start(); } else if (tempFile.getName().endsWith("rss")) { GeoRssImporter rss = new GeoRssImporter(); MapImporter importer = new MapImporter(rss, tempFile, nodeMap, this, null); importer.start(); } else { Logger.log(Logger.ERR, "Error in NetworkLayer.updateData() - Unsupported Data Type"); } } } } catch (Exception e) { Logger.log(Logger.ERR, "Error in NetworkLayer.updateData() - " + e); } } /** * Writes this Layer as KML. * * @param xmlWriter */ @Override public void toXML(XmlOutput xmlWriter) { try { xmlWriter.openTag("NetworkLayer"); xmlWriter.writeTag("Name", layerName); xmlWriter.writeTag("href", address); xmlWriter.writeTag("PointClass", defaultPointClass); xmlWriter.writeTag("RefreshInterval", Float.toString(refreshInterval)); if (refreshMode == NetworkLayer.NEVER) { xmlWriter.writeTag("RefreshMode", "never"); } else if (refreshMode == NetworkLayer.ON_CHANGE) { xmlWriter.writeTag("RefreshMode", "onChange"); } else if (refreshMode == NetworkLayer.ON_EXPIRE) { xmlWriter.writeTag("RefreshMode", "onExpire"); } else if (refreshMode == NetworkLayer.ON_INTERVAL) { xmlWriter.writeTag("RefreshMode", "onInterval"); } layerVisibility.toXML(xmlWriter); if (layerDescription != null) { if (!layerDescription.equals("") && !layerDescription.equalsIgnoreCase("null")) xmlWriter.writeTag("description", layerDescription); } xmlWriter.closeTag("NetworkLayer"); } catch (Exception e) { Logger.log(Logger.ERR, "Error in NetworkLayer.toXML(XmlWriter) - " + e); } } }