/*******************************************************************************
* Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License 3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
******************************************************************************/
package com.opendoorlogistics.core.gis.map.tiled;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import com.opendoorlogistics.api.geometry.LatLongToScreen;
import com.opendoorlogistics.core.cache.RecentlyUsedCache;
import com.opendoorlogistics.core.gis.map.DatastoreRenderer;
import com.opendoorlogistics.core.gis.map.ObjectRenderer;
import com.opendoorlogistics.core.gis.map.data.DrawableObject;
import com.opendoorlogistics.core.gis.map.tiled.DrawableObjectLayer.LayerType;
import com.opendoorlogistics.core.utils.strings.StandardisedCache;
import com.opendoorlogistics.core.utils.strings.StandardisedStringSet;
import com.opendoorlogistics.core.utils.strings.StandardisedStringTreeMap;
import com.opendoorlogistics.core.utils.strings.Strings;
public class NOPLManager {
final private StandardisedStringTreeMap<LayerCache> currentLayers = new StandardisedStringTreeMap<>(false);
final private ObjectRenderer renderer =new DatastoreRenderer(){
/**
* Layer rendering handles poly colour itself so we need to ensure
* the render doesn't set it here...
*/
@Override
protected Color getPolygonBorderColour(Color polyCol){
return polyCol;
}
};
private class LayerCache{
final RecentlyUsedCache tileCache = new RecentlyUsedCache("nopl-layer-tile-cache",64 * 1024 * 1024);
final DrawableObjectLayer layer;
LayerCache(DrawableObjectLayer layer) {
this.layer = layer;
}
}
synchronized List<DrawableObjectLayer> update(Iterable<? extends DrawableObject> newDrawables){
// get new layers by splitting the drawables
List<DrawableObjectLayer> newLayers = splitDrawablesIntoLayers(newDrawables);
StandardisedStringSet newLayerIds = new StandardisedStringSet(false);
// parse all new NOVLP layers
for(DrawableObjectLayer newLayer:newLayers){
if(newLayer.getType() != LayerType.NOVLPL){
continue;
}
String groupId = Strings.std(newLayer.getNOVLPLGroupId());
newLayerIds.add(groupId);
boolean addLayer=false;
LayerCache cachedLayer = synchronisedGetLayer(groupId);
if(cachedLayer==null){
// The layer is new so add it.
addLayer = true;
}else{
// Check each geometry object in the new layer exists in the old one
for(DrawableObject obj:newLayer){
if(obj.getGeometry()!=null){
if(cachedLayer.layer.hasGeom(obj.getGeometry())==false){
// The new layer contains one or more geoms not already in the cached layer, invalidating it.
// Remove the old cached layer and then add the new one.
currentLayers.remove(groupId);
addLayer = true;
break;
}
}
}
}
if(addLayer){
currentLayers.put(groupId, new LayerCache(newLayer));
}
}
// remove any layers which are no longer used
ArrayList<String> currentIds = new ArrayList<>(this.currentLayers.keySet());
for(String id:currentIds){
if(newLayerIds.contains(id)==false){
this.currentLayers.remove(id);
}
}
return newLayers;
}
/**
* Split input objects into layers based on their order and the non-overlapping polygon group key
* @param drawables
* @return
*/
private List<DrawableObjectLayer> splitDrawablesIntoLayers(Iterable<? extends DrawableObject> drawables){
StandardisedCache standardiser = new StandardisedCache();
// split by consecutive NOLP group key
ArrayList<DrawableObjectLayer> ret = new ArrayList<>();
DrawableObjectLayer currentLayer = null;
for(DrawableObject o:drawables){
// create new layer if needed
boolean novlpl = Strings.isEmpty(o.getNonOverlappingPolygonLayerGroupKey())==false;
if(novlpl){
String stdGroup = standardiser.std(o.getNonOverlappingPolygonLayerGroupKey());
if(currentLayer == null || currentLayer.getType() != DrawableObjectLayer.LayerType.NOVLPL || currentLayer.getNOVLPLGroupId().equals(stdGroup)==false){
// scan for matching an existing polygon layer
currentLayer = null;
for(DrawableObjectLayer layer:ret){
if(layer.getType() ==DrawableObjectLayer.LayerType.NOVLPL && layer.getNOVLPLGroupId().equals(novlpl) ){
// User has probably screwed up as same id has been used non-consecutively.
// Just render everything in the first layer.
currentLayer = layer;
}
}
// no found, create new
if(currentLayer==null){
currentLayer = new DrawableObjectLayer(stdGroup);
ret.add(currentLayer);
}
}
}else{
// normal case
if(currentLayer ==null || currentLayer.getType() != DrawableObjectLayer.LayerType.NORMAL){
currentLayer = new DrawableObjectLayer();
ret.add(currentLayer);
}
}
// add object to current layer
currentLayer.add(o);
}
return ret;
}
// private List<DrawableObjectLayer> splitDrawablesIntoLayersV2(Iterable<? extends DrawableObject> drawables){
// StandardisedCache standardiser = new StandardisedCache();
//
// // split by consecutive NOLP group key
// ArrayList<DrawableObjectLayer> ret = new ArrayList<>();
// DrawableObjectLayer currentLayer = null;
// for(DrawableObject o:drawables){
//
// // create new layer if needed
// boolean novlpl = Strings.isEmpty(o.getNonOverlappingPolygonLayerGroupKey())==false;
// if(novlpl){
// String stdGroup = standardiser.std(o.getNonOverlappingPolygonLayerGroupKey());
// if(currentLayer == null || currentLayer.getType() != DrawableObjectLayer.LayerType.NOVLPL || currentLayer.getNOVLPLGroupId().equals(stdGroup)==false){
//
// // scan for matching an existing polygon layer
// currentLayer = null;
// for(DrawableObjectLayer layer:ret){
// if(layer.getType() ==DrawableObjectLayer.LayerType.NOVLPL && layer.getNOVLPLGroupId().equals(novlpl) ){
// // User has probably screwed up as same id has been used non-consecutively.
// // Just render everything in the first layer.
// currentLayer = layer;
// }
// }
//
// // no found, create new
// if(currentLayer==null){
// currentLayer = new DrawableObjectLayer(stdGroup);
// ret.add(currentLayer);
// }
// }
//
// }else{
// // normal case
// if(currentLayer ==null || currentLayer.getType() != DrawableObjectLayer.LayerType.NORMAL){
// currentLayer = new DrawableObjectLayer();
// ret.add(currentLayer);
// }
// }
//
// // add object to current layer
// currentLayer.add(o);
//
// }
//
// return ret;
// }
/**
* Get the tile or create it if it doesn't exist yet
* @param layerId
* @param position
* @param converter
* @param renderer
* @return
*/
NOVLPolyLayerTile getTile(String layerId, TilePosition position,LatLongToScreen converter){
// get layer, synchronising so we wait for any update to have finished
LayerCache layerCache = synchronisedGetLayer(layerId);
if(layerCache == null){
return null;
}
NOVLPolyLayerTile ret =(NOVLPolyLayerTile)layerCache.tileCache.get(position);
if(ret==null){
ret = new NOVLPolyLayerTile(converter, renderer, layerCache.layer);
layerCache.tileCache.put(position, ret, ret.getSizeInBytes());
}
return ret;
}
/**
* Retrieve the layer in a thread-synchronised manner
* @param layerId
* @return
*/
synchronized private LayerCache synchronisedGetLayer(String layerId) {
LayerCache layerCache = currentLayers.get(layerId);
return layerCache;
}
}