package com.opendoorlogistics.studio.components.map;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.AbstractAction;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.components.ComponentControlLauncherApi;
import com.opendoorlogistics.api.components.ComponentControlLauncherApi.ControlLauncherCallback;
import com.opendoorlogistics.api.components.ComponentExecutionApi;
import com.opendoorlogistics.api.components.PredefinedTags;
import com.opendoorlogistics.api.geometry.LatLong;
import com.opendoorlogistics.api.geometry.LatLongToScreen;
import com.opendoorlogistics.api.standardcomponents.map.MapActionFactory;
import com.opendoorlogistics.api.standardcomponents.map.MapApi;
import com.opendoorlogistics.api.standardcomponents.map.MapDataApi;
import com.opendoorlogistics.api.standardcomponents.map.MapMode;
import com.opendoorlogistics.api.standardcomponents.map.MapPlugin;
import com.opendoorlogistics.api.standardcomponents.map.MapSelectionList;
import com.opendoorlogistics.api.standardcomponents.map.MapToolbar;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLDatastoreAlterable;
import com.opendoorlogistics.api.tables.ODLDatastoreUndoable;
import com.opendoorlogistics.api.tables.ODLListener;
import com.opendoorlogistics.api.tables.ODLTable;
import com.opendoorlogistics.api.tables.ODLTableAlterable;
import com.opendoorlogistics.api.tables.ODLTableReadOnly;
import com.opendoorlogistics.api.tables.TableFlags;
import com.opendoorlogistics.api.tables.beans.BeanMappedRow;
import com.opendoorlogistics.api.ui.Disposable;
import com.opendoorlogistics.codefromweb.jxmapviewer2.fork.swingx.mapviewer.GeoPosition;
import com.opendoorlogistics.codefromweb.jxmapviewer2.fork.swingx.mapviewer.TileFactoryInfo;
import com.opendoorlogistics.core.api.impl.ODLApiImpl;
import com.opendoorlogistics.core.components.ODLGlobalComponents;
import com.opendoorlogistics.core.gis.map.DatastoreRenderer;
import com.opendoorlogistics.core.gis.map.JXMapUtils;
import com.opendoorlogistics.core.gis.map.MapUtils;
import com.opendoorlogistics.core.gis.map.RenderProperties;
import com.opendoorlogistics.core.gis.map.background.BackgroundTileFactorySingleton;
import com.opendoorlogistics.core.gis.map.background.ODLTileFactory;
import com.opendoorlogistics.core.gis.map.data.DrawableObject;
import com.opendoorlogistics.core.gis.map.data.DrawableObjectImpl;
import com.opendoorlogistics.core.gis.map.data.LatLongBoundingBox;
import com.opendoorlogistics.core.gis.map.tiled.TileCacheRenderer;
import com.opendoorlogistics.core.gis.map.tiled.TileCacheRenderer.TileReadyListener;
import com.opendoorlogistics.core.tables.beans.BeanMapping.BeanTableMappingImpl;
import com.opendoorlogistics.core.tables.decorators.datastores.ListenerDecorator;
import com.opendoorlogistics.core.tables.decorators.datastores.undoredo.UndoRedoDecorator;
import com.opendoorlogistics.core.tables.utils.TableUtils;
import com.opendoorlogistics.core.utils.SetUtils;
import com.opendoorlogistics.core.utils.ui.PopupMenuMouseAdapter;
import com.opendoorlogistics.core.utils.ui.ShowPanel;
import com.opendoorlogistics.core.utils.ui.SwingUtils;
import com.opendoorlogistics.studio.GlobalMapSelectedRowsManager;
import com.opendoorlogistics.studio.InitialiseStudio;
import com.opendoorlogistics.studio.components.map.plugins.CustomTooltipPlugin;
import com.opendoorlogistics.studio.components.map.plugins.SummariseFieldValuesTooltipPlugin;
import com.opendoorlogistics.studio.components.map.plugins.utils.PluginUtils;
import gnu.trove.list.array.TLongArrayList;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.set.hash.TLongHashSet;
/**
* The implementation of the api object is just an aggregate of other objects
* which pumps messages between them as needed.
*
* @author Phil
*
*/
public class MapApiImpl extends MapApiListenersImpl implements MapApi, Disposable , MapSelectionList{
private final MapSelectionState selectionState;
private final ViewPosition position;
private final DisposableMapPanel containerLevel1Panel;
private final JPanel containerLevel2Panel;
private final MapToolbar toolBar;
private final ODLDatastoreUndoable<? extends ODLTableAlterable> globalDs;
private final JPanel[] sidePanels = new JPanel[PanelPosition.values().length];
private final ComponentControlLauncherApi componentControlLauncherApi;
private final MapViewPanel mapViewPanel;
private final ExecutorService executorService = Executors.newFixedThreadPool(1);
private MapMode defaultMode;
private TileCacheRenderer renderer;
private long renderFlags = RenderProperties.SHOW_ALL;
private MapMode mode;
private FilteredTables filtered;
private BeanMappedObjects objs;
private ODLDatastore<? extends ODLTable> mapDatastore;
private MeasureComponents lastMeasure;
private BufferedImage disabledPaintImage;
private boolean isPendingInitContainerPanels=false;
private volatile boolean isDisposed=false;
public class DisposableMapPanel extends JPanel implements Disposable {
@Override
public void dispose() {
MapApiImpl.this.dispose();
}
public MapApiImpl getApi(){
return MapApiImpl.this;
}
}
/**
* Disable painting temporarily and take an image of the current component
* @param disabled
*/
private void setDisablePaint(boolean disabled){
if(disabled != mapViewPanel.isDisablePaint()){
if(disabled){
// take image to show
disabledPaintImage = new BufferedImage(containerLevel2Panel.getWidth(), containerLevel2Panel.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = disabledPaintImage.getGraphics();
g.setColor(containerLevel2Panel.getForeground());
g.setFont(containerLevel2Panel.getFont());
containerLevel2Panel.paintAll(g);
g.dispose();
}
mapViewPanel.setDisablePaint(disabled);
}
}
public MapApiImpl(Iterable<MapPlugin> plugins, ComponentControlLauncherApi componentControlLauncherApi,
ODLDatastoreUndoable<? extends ODLTableAlterable> globalDs, ODLDatastore<? extends ODLTable> mapDatastore) {
this(plugins, componentControlLauncherApi, globalDs, BeanMappedObjects.create(mapDatastore), mapDatastore);
}
private MapApiImpl(Iterable<MapPlugin> plugins, ComponentControlLauncherApi componentControlLauncherApi,
ODLDatastoreUndoable<? extends ODLTableAlterable> globalDs,BeanMappedObjects beanMappedObjects, ODLDatastore<? extends ODLTable> mapDatastore) {
this.globalDs = globalDs;
this.componentControlLauncherApi = componentControlLauncherApi;
this.selectionState = new MapSelectionState();
this.position = new ViewPosition();
// Init renderer and set callback for when our own tiles are loaded
renderer = new TileCacheRenderer();
renderer.addTileReadyListener(new TileReadyListener() {
@Override
public void tileReady(final Rectangle2D worldBitmapBounds, final Object zoom) {
SwingUtils.invokeLaterOnEDT(new Runnable() {
@Override
public void run() {
// repaint if an on-screen tile has been updated
if (zoom.equals(getZoom())) {
Rectangle2D current = createImmutableConverter().getViewportWorldBitmapScreenPosition();
if (current.intersects(worldBitmapBounds)) {
repaint(false);
}
}
}
});
}
});
// Init container panel
containerLevel1Panel = new DisposableMapPanel();
containerLevel1Panel.setPreferredSize(new Dimension(700, 600));
containerLevel1Panel.setLayout(new BorderLayout());
containerLevel2Panel = new JPanel() {
@Override
public void paint(Graphics g) {
super.paint(g);
lastMeasure = new MeasureComponents();
// Painting updates control sizes which are needed various calculations.
// We therefore just paint over the control if painting is 'disabled' so
// we don't see controls in odd positions.
if(mapViewPanel.isDisablePaint()){
if(disabledPaintImage!=null){
g.drawImage(disabledPaintImage, 0, 0, null);
}else{
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
}
};
containerLevel2Panel.setLayout(new BorderLayout());
containerLevel1Panel.add(containerLevel2Panel, BorderLayout.CENTER);
// init the map panel
mapViewPanel = new MapViewPanel(this) {
@Override
protected void paintMapObjects(Graphics2D g) {
renderer.renderObjects(g, mapViewPanel.createImmutableConverter(), renderFlags, selectionState.copySet());
}
@Override
protected void paintPluginOverlay(Graphics2D g) {
// the plugins
fireOnPaintListeners(MapApiImpl.this, g);
// the mode
if (mode != null) {
mode.paint(MapApiImpl.this, g);
}
}
@Override
public String getToolTipText(MouseEvent event) {
StringBuilder builder = new StringBuilder();
Rectangle rect = new Rectangle(event.getX() - 1, event.getY() - 1, 2, 2);
List<DrawableObject> within = DatastoreRenderer.getObjectsWithinRectangle(filtered!=null? filtered.activeFiltered:null, createImmutableConverter(), rect, false);
TLongHashSet ids = new TLongHashSet();
for(DrawableObject d : within){
if(d.getGlobalRowId()!=-1){
ids.add(d.getGlobalRowId());
}
}
fireTooltipListeners(MapApiImpl.this, event, ids.toArray(), builder);
if(builder.length()==0){
return null;
}
return builder.toString();
}
};
mapViewPanel.addMouseListener(this);
mapViewPanel.addMouseMotionListener(this);
mapViewPanel.addKeyListener(this);
mapViewPanel.setToolTipText(""); // add dummy tooltip text so getToolTipText method is called
// Init the plugins before building the toolbar (so they can add to it)
if (plugins != null) {
for (MapPlugin factory : plugins) {
factory.initMap(this);
}
}
// Init toolbar
toolBar = new MapToolbarImpl();
fireBuildToolbarListeners(this, toolBar);
containerLevel1Panel.add(toolBar.getComponent(), BorderLayout.NORTH);
// Add all controls to the main panel
initContainerPanel(false);
// Init right-click menu on the map
mapViewPanel.addMouseListener(new PopupMenuMouseAdapter() {
@Override
protected void launchMenu(MouseEvent me) {
MapPopupMenuImpl menu = new MapPopupMenuImpl();
fireBuildContextMenuListeners(MapApiImpl.this, menu);
menu.show(me.getComponent(), me.getX(), me.getY());
}
});
setObjects(beanMappedObjects,mapDatastore);
// Zoom all by default
addOnGeometryLoadedCallback(new Runnable() {
@Override
public void run() {
// delay by one event cycle to ensure map control is initialised (sometimes it has zero dimensions
// otherwise ... screwing up the calculation)
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setViewToBestFit(getMapDataApi().getFilteredAllLayersTable(true));
}
});
}
});
}
@Override
public void mouseClicked(MouseEvent e) {
mapViewPanel.requestFocus();
super.mouseClicked(e);
}
@Override
public void mousePressed(MouseEvent e) {
mapViewPanel.requestFocus();
super.mousePressed(e);
}
@Override
public void mouseDragged(MouseEvent e) {
mapViewPanel.requestFocus();
super.mouseDragged(e);
}
@Override
public void mouseMoved(MouseEvent e) {
mapViewPanel.requestFocus();
super.mouseMoved(e);
}
@Override
public void mouseEntered(MouseEvent e) {
mapViewPanel.requestFocus();
super.mouseEntered(e);
}
// public void setObjects(ODLDatastore<? extends ODLTable> newMapDatastore) {
//
// // firePreObjectsChangedListener(this, mapDatastore);
//
// updateObjectFiltering(newMapDatastore);
//
//
// }
public DisposableMapPanel getPanel() {
return containerLevel1Panel;
}
private static class FilteredTables {
final FindDrawableTables unfilteredTables;
// final Iterable<? extends DrawableObject> activeUnfiltered;
final Iterable<? extends DrawableObject> activeFiltered;
final LayeredDrawables allFiltered;
final MapApiImpl api;
final BeanMappedObjects objs;
FilteredTables(BeanMappedObjects objs, ODLDatastore<? extends ODLTable> mapDatastore, MapApiImpl api,boolean isFiltered){
this.api = api;
this.objs = objs;
api.fireStartObjectFiltering(api, mapDatastore);
unfilteredTables = new FindDrawableTables(mapDatastore);
ArrayList< DrawableObject> activeList = new ArrayList<DrawableObject>(unfilteredTables.activeTable!=null ? unfilteredTables.activeTable.getRowCount():0);
// activeUnfiltered = activeList;
activeFiltered = filter(1,unfilteredTables.activeTable,isFiltered, activeList);
allFiltered = new LayeredDrawables(filter(0,unfilteredTables.background,isFiltered,null), activeFiltered, filter(2,unfilteredTables.foreground,isFiltered,null));
api.fireEndObjectFiltering(api);
}
private Iterable<? extends DrawableObject> filter(int tableOrder, ODLTableReadOnly table, boolean isFiltered,List< DrawableObject> saveAllToList ) {
if (table == null) {
return null;
}
int n = table.getRowCount();
ArrayList<DrawableObject> ret = new ArrayList<DrawableObject>(n);
for (int row = 0; row < n; row++) {
// save to list if needed
DrawableObject obj =objs.get(tableOrder, row);
if(saveAllToList!=null && obj!=null){
saveAllToList.add(obj);
}
if(obj!=null){
if(saveAllToList!=null){
saveAllToList.add(obj);
}
boolean accept =!isFiltered || api.fireFilterObject(api, table, row);
if(accept){
ret.add(obj);
}
}
}
return ret;
}
}
@Override
public void updateObjectFiltering() {
filtered = new FilteredTables(objs,mapDatastore, MapApiImpl.this,true);
renderer.setObjects(filtered.allFiltered);
mapViewPanel.repaint();
}
public void setObjects(ODLDatastore<? extends ODLTable> newMapDatastore) {
setObjects(BeanMappedObjects.create(newMapDatastore),newMapDatastore);
}
private void setObjects(BeanMappedObjects objs,ODLDatastore<? extends ODLTable> newMapDatastore) {
this.objs = objs;
mapDatastore = newMapDatastore;
updateObjectFiltering();
fireObjectsChangedListeners(MapApiImpl.this);
// Update selected ids. don't allow anything to be selected that's not in the active table,
// however we do allow filtered out objects to stay selected (needed for polygon editing plugin).
// We do this on the table instead of the drawableobjects, as the bean conversion to drawableobjects
// filters out anything with null geometry and long lats.
TLongHashSet newSelected = new TLongHashSet();
ODLTableReadOnly unfilteredActive = filtered.unfilteredTables.activeTable;
if(unfilteredActive!=null){
int n = unfilteredActive.getRowCount();
for(int i =0 ; i < n ; i++){
long rowid = unfilteredActive.getRowId(i);
if (selectionState.contains(rowid)) {
newSelected.add(rowid);
}
}
}
if (!selectionState.equals(newSelected)) {
setSelectedIds(newSelected.toArray());
}
}
@Override
public int getZoom() {
return position.getZoom();
}
@Override
public Point2D getWorldBitmapMapCentre() {
return position.getCenter();
}
// @Override
// public void setReuseImageOnNextPaint() {
// // TODO Auto-generated method stub
//
// }
@Override
public void repaint(boolean repaintPluginOverlapOnly) {
mapViewPanel.repaint(repaintPluginOverlapOnly);
}
@Override
public long[] getSelectedIds() {
return selectionState.copyIds();
}
@Override
public long[] getSelectableIdsWithinPixelRectangle(Rectangle screenCoordinatesRectangle) {
TLongArrayList within = DatastoreRenderer.getWithinRectangle(filtered!=null? filtered.activeFiltered:null, createImmutableConverter(), screenCoordinatesRectangle, true);
return within.toArray();
}
@Override
public void clearSelection() {
setSelectedIds(new long[] {});
}
@Override
public void setSelectedIds(long... ids) {
if(selectionState.set(ids)){
fireSelectionChangedListeners(this);
}
repaint(false);
}
@Override
public Component getMapWindowComponent() {
return mapViewPanel;
}
@Override
public void setCursor(Cursor cursor) {
// TODO Auto-generated method stub
}
@Override
public void setMapMode(MapMode newMode) {
MapMode oldMode = mode;
if (oldMode != null) {
removeMouseInputListener(oldMode);
removeObjectsChangedListener(oldMode);
oldMode.onExitMode(this);
}
// turn the cursor back to normal
mapViewPanel.setCursor(Cursor.getDefaultCursor());
// if new mode if null then choose our default mode
if(newMode==null){
newMode = defaultMode;
}
fireModeChangingListener(this, oldMode, newMode);
mode = newMode;
fireModeChangedListener(this, oldMode, mode);
if (newMode != null) {
registerMouseInputListener(newMode, Integer.MIN_VALUE);
registerObjectsChangedListener(newMode, 0);
newMode.onEnterMode(this);
if (newMode.getCursor() != null) {
mapViewPanel.setCursor(newMode.getCursor());
}
}
}
@Override
public long getRenderFlags() {
return renderFlags;
}
@Override
public void setRenderFlags(long flags) {
this.renderFlags = flags;
mapViewPanel.repaint();
}
@Override
public LatLongToScreen createImmutableConverter() {
return mapViewPanel.createImmutableConverter();
}
@Override
public void dispose() {
isDisposed = true;
// unselect everything then tell the listeners
setSelectedIds(new long[]{});
fireSelectionChangedListeners(this);
renderer.dispose();
fireDisposedListeners(this);
executorService.shutdown();
for (JPanel d : sidePanels) {
if (d != null) {
((Disposable) d).dispose();
}
}
mapViewPanel.dispose();
}
@Override
public void setView(int zoom, Point2D centreInPixels) {
position.setCenter(centreInPixels);
position.setZoom(zoom);
fireViewChangedListeners(this);
mapViewPanel.repaint();
}
@Override
public int getMinZoom() {
return position.getMinZoom();
}
@Override
public int getMaxZoom() {
return position.getMaxZoom();
}
@Override
public boolean isSelectedId(long id) {
return selectionState.contains(id);
}
public static void main(String[] args) {
InitialiseStudio.initialise(false);
final ODLDatastoreAlterable<? extends ODLTableAlterable> exampleds = MapUtils.createExampleDatastore();
ODLDatastoreAlterable<? extends ODLTableAlterable> listener = new ListenerDecorator(ODLTableAlterable.class, exampleds);
ODLDatastoreUndoable<? extends ODLTableAlterable> undoable = new UndoRedoDecorator<ODLTableAlterable>(ODLTableAlterable.class, listener);
MapConfig config = new MapConfig();
config.setUseCustomTooltips(true);
// exampleds.getTableAt(0).setFlags(exampleds.getTableAt(0).getFlags() & ~TableFlags.UI_DELETE_ALLOWED);
GlobalMapSelectedRowsManager gsm = new GlobalMapSelectedRowsManager() {
@Override
public void onMapSelectedChanged() {
// TODO Auto-generated method stub
}
};
List<MapPlugin> plugins = getPlugins(config);
// Test button
plugins.add(new MapPlugin() {
@Override
public String getId(){
return "TEST_PLUGIN";
}
@Override
public void initMap(MapApi api) {
api.registerOnBuildToolbarListener(new OnBuildToolbarListener() {
@Override
public void onBuildToolbar(final MapApi api, MapToolbar toolBar) {
toolBar.add(new AbstractAction("TEST") {
@Override
public void actionPerformed(ActionEvent e) {
// test changing permissions
ODLTableAlterable table = exampleds.getTableAt(0);
long flags = table.getFlags();
if( (flags & TableFlags.UI_DELETE_ALLOWED) !=0){
table.setFlags(flags & ~TableFlags.UI_DELETE_ALLOWED);
}
else if( (flags & TableFlags.UI_INSERT_ALLOWED) !=0){
table.setFlags(flags & ~TableFlags.UI_INSERT_ALLOWED);
}
else if( (flags & TableFlags.UI_SET_ALLOWED) !=0){
table.setFlags(flags & ~TableFlags.UI_SET_ALLOWED);
}
((MapApiImpl)api).setObjects(BeanMappedObjects.create(exampleds),exampleds);
}
});
}
}, Integer.MIN_VALUE);
}
});
final ODLApi api = new ODLApiImpl();
ComponentControlLauncherApi dummyApi = new ComponentControlLauncherApi() {
@Override
public JPanel getRegisteredPanel(String panelId) {
// TODO Auto-generated method stub
return null;
}
@Override
public <T extends JPanel & Disposable> boolean registerPanel(String panelId, String title, T panel, boolean refreshable) {
// TODO Auto-generated method stub
return false;
}
@Override
public List<JPanel> getRegisteredPanels() {
// TODO Auto-generated method stub
return null;
}
@Override
public void disposeRegisteredPanel(JPanel panel) {
// TODO Auto-generated method stub
}
@Override
public void setTitle(JPanel panel, String title) {
// TODO Auto-generated method stub
}
@Override
public void toFront(JPanel panel) {
// TODO Auto-generated method stub
}
@Override
public ODLApi getApi() {
return api;
}
@Override
public ODLDatastoreUndoable<? extends ODLTableAlterable> getGlobalDatastore() {
return undoable;
}
@Override
public MapSelectionListRegister getMapSelectionListRegister() {
// TODO Auto-generated method stub
return gsm;
}
};
final MapApiImpl mapApi = new MapApiImpl(plugins, dummyApi, undoable,BeanMappedObjects.create(exampleds), exampleds);
mapApi.connectToGSM(gsm);
undoable.addListener(new ODLListener() {
@Override
public void tableChanged(int tableId, int firstRow, int lastRow) {
mapApi.setObjects(BeanMappedObjects.create(exampleds),exampleds);
}
@Override
public ODLListenerType getType() {
return ODLListenerType.TABLE_CHANGED;
}
@Override
public void datastoreStructureChanged() {
// TODO Auto-generated method stub
}
}, exampleds.getTableAt(0).getImmutableId());
ShowPanel.showPanel(mapApi.getPanel(), true);
}
@Override
public Rectangle getWorldBitmapViewport() {
return mapViewPanel.getViewportBounds();
}
public void connectToGSM(final MapSelectionListRegister gsm) {
// register this selection list globally
gsm.registerMapSelectionList(this);
// unregister it when the map closes
registerDisposedListener(new OnDisposedListener() {
@Override
public void onDispose(MapApi api) {
gsm.unregisterMapSelectionList( (MapApiImpl)api);
}
}, 0);
// and also tell the register when the selection changes
registerSelectionChanged(new OnChangeListener() {
@Override
public void onChanged(MapApi api) {
gsm.onMapSelectedChanged();
}
}, 0);
}
private void addOnGeometryLoadedCallback(final Runnable runnable) {
class Handler implements ActionListener {
Timer timer;
Handler() {
timer = new Timer(100, this);
timer.start();
}
@Override
public void actionPerformed(ActionEvent e) {
if (filtered != null) {
for (DrawableObject pnt : filtered.allFiltered) {
if (pnt.getGeometry() != null && pnt.getGeometry().isLoaded() == false) {
return;
}
}
}
timer.stop();
runnable.run();
}
}
new Handler();
}
@Override
public void setViewToBestFit(ODLTableReadOnly drawables) {
double defaultEdgeSizeDegrees = 0.1;
double maxFraction = 0.975;
Iterable<? extends DrawableObject> objs = MapUtils.getDrawables(drawables);
LatLongBoundingBox llbb = MapUtils.getLatLongBoundingBox(objs, null);
class Helper {
Set<GeoPosition> createGeopositionsSquareAroundPoint(double latitude, double longitude, double edgeSizeDegrees) {
HashSet<GeoPosition> dummies = new HashSet<>();
for (int lat = -1; lat <= 1; lat += 2) {
for (int lng = -1; lng <= 1; lng += 2) {
dummies.add(new GeoPosition(latitude + lat * edgeSizeDegrees, longitude + lng * edgeSizeDegrees));
}
}
return dummies;
}
}
Helper helper = new Helper();
ODLTileFactory tileFactory = BackgroundTileFactorySingleton.getFactory();
TileFactoryInfo info = tileFactory.getInfo();
Set<GeoPosition> positions = llbb.getCornerSet();
if (positions.size() > 0) {
if (positions.size() == 1) {
GeoPosition pos = SetUtils.getFirst(positions);
positions = helper.createGeopositionsSquareAroundPoint(pos.getLatitude(), pos.getLongitude(), defaultEdgeSizeDegrees);
}
if (info != null) {
Rectangle viewBounds = getWorldBitmapViewport();
int bfz = JXMapUtils.getBestFitZoom(info, positions, viewBounds.width, viewBounds.height, maxFraction).getFirst();
Rectangle2D bounds = JXMapUtils.generateBoundingRect(info, positions, bfz);
// GeoPosition centre = tileFactory.pixelToGeo(new
// Point2D.Double(bounds.getCenterX(), bounds.getCenterY()),
// bfz);
setView(bfz, new Point2D.Double(bounds.getCenterX(), bounds.getCenterY()));
}
}
}
@Override
public Dimension getWorldBitmapMapSize(int zoom) {
zoom = clampZoom(zoom);
Dimension inTiles = BackgroundTileFactorySingleton.getFactory().getMapSize(zoom);
int sz = BackgroundTileFactorySingleton.getFactory().getTileSize(zoom);
return new Dimension(inTiles.width * sz, inTiles.height * sz);
}
private int clampZoom(int zoom) {
if (zoom > getMaxZoom()) {
zoom = getMaxZoom();
}
if (zoom < getMinZoom()) {
zoom = getMinZoom();
}
return zoom;
}
@Override
public void setZoom(int newZoom) {
newZoom = clampZoom(newZoom);
int oldZoom = getZoom();
Dimension oldSize = getWorldBitmapMapSize(oldZoom);
Dimension newSize = getWorldBitmapMapSize(newZoom);
double xRatio = (double) newSize.width / oldSize.width;
double yRatio = (double) newSize.height / oldSize.height;
Point2D.Double newCentre = new Point2D.Double(getWorldBitmapMapCentre().getX() * xRatio, getWorldBitmapMapCentre().getY() * yRatio);
setView(newZoom, newCentre);
}
@Override
public <T extends JPanel & Disposable> void setSidePanel(T panel, PanelPosition pos) {
int i = pos.ordinal();
if (sidePanels[i] != panel) {
if (sidePanels[i] != null && sidePanels[i] != panel) {
((Disposable) sidePanels[i]).dispose();
}
sidePanels[i] = panel;
initContainerPanel(true);
}
}
// private Point getMapScreenAbsCentre(){
// Point ret = SwingUtilities.convertPoint(mapViewPanel, mapViewPanel.getX(), mapViewPanel.getY(), containerLevel1Panel);
// ret.x += mapViewPanel.getWidth()/2;
// ret.y += mapViewPanel.getHeight()/2;
// return ret;
// }
private class MeasureComponents{
int leftWidth;
int rightWidth;
int bottomHeight;
int width;
int height;
public MeasureComponents() {
width = containerLevel2Panel.getWidth();
height = containerLevel2Panel.getHeight();
int dividerSize = new JSplitPane().getDividerSize();
if(panel(PanelPosition.LEFT)!=null && panel(PanelPosition.LEFT).isVisible()){
leftWidth = panel(PanelPosition.LEFT).getWidth();
leftWidth += dividerSize;
}
if(panel(PanelPosition.RIGHT)!=null&& panel(PanelPosition.RIGHT).isVisible()){
rightWidth = panel(PanelPosition.RIGHT).getWidth();
rightWidth += dividerSize;
}
if(panel(PanelPosition.BOTTOM)!=null&& panel(PanelPosition.BOTTOM).isVisible()){
bottomHeight = panel(PanelPosition.BOTTOM).getHeight();
bottomHeight += dividerSize;
}
}
private JPanel panel(PanelPosition p){
return sidePanels[p.ordinal()];
}
public Point calculateMapCentre(){
int mapWidth = width - leftWidth - rightWidth;
int mapHeight = height - bottomHeight;
int x= leftWidth + mapWidth/2;
int y = mapHeight / 2;
return new Point(x, y);
}
}
private class InitContainerPanel implements Runnable{
final boolean isReinit;
public InitContainerPanel(boolean isReinit) {
this.isReinit = isReinit;
}
@Override
public void run() {
isPendingInitContainerPanels = false;
// if reinitialising, get original map centre point and remove all components
final Point mapPoint0;
if(isReinit){
mapPoint0 = (lastMeasure!=null ?lastMeasure: new MeasureComponents()).calculateMapCentre();
setDisablePaint(true);
containerLevel2Panel.removeAll();
}else{
mapPoint0 = null;
}
// get things to run across several EDT cycles
final ArrayList<Runnable> runnables = new ArrayList<Runnable>();
Component component = mapViewPanel;
if (sidePanels[PanelPosition.RIGHT.ordinal()] != null) {
JSplitPane splitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, component, sidePanels[PanelPosition.RIGHT.ordinal()]);
splitter.setResizeWeight(0.8);
runnables.add(new Runnable() {
@Override
public void run() {
Container container = splitter.getParent();
if (container != null) {
int width = container.getWidth();
splitter.setDividerLocation(width - width / 5);
}
}
});
component = splitter;
}
if (sidePanels[PanelPosition.LEFT.ordinal()] != null) {
JSplitPane splitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sidePanels[PanelPosition.LEFT.ordinal()], component);
splitter.setResizeWeight(0.2);
runnables.add(new Runnable() {
@Override
public void run() {
Container container = splitter.getParent();
if (container != null) {
int width = container.getWidth();
splitter.setDividerLocation(width / 5);
}
}
});
component = splitter;
}
if (sidePanels[PanelPosition.BOTTOM.ordinal()] != null) {
final JSplitPane splitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT, component, sidePanels[PanelPosition.BOTTOM.ordinal()]);
splitter.setResizeWeight(0.8);
runnables.add(new Runnable() {
@Override
public void run() {
Container container = splitter.getParent();
if (container != null) {
int height = container.getHeight();
splitter.setDividerLocation(height - height / 4);
}
}
});
component = splitter;
}
containerLevel2Panel.add(component, BorderLayout.CENTER);
// fix the map centre so it doesn't change
if (isReinit) {
containerLevel2Panel.revalidate();
// Add the runnable to fix the map positions, including a null runnable before hand
// so we get an extra EDT cycle which ensures measurements are correct
runnables.add(null);
runnables.add(new Runnable() {
@Override
public void run() {
Point mapPoint1 = new MeasureComponents().calculateMapCentre();
Point offset = new Point(mapPoint1.x - mapPoint0.x, mapPoint1.y - mapPoint0.y);
Point2D centre = getWorldBitmapMapCentre();
setDisablePaint(false);
setView(getZoom(), new Point2D.Double(centre.getX() + offset.getX(), centre.getY() + offset.getY()));
containerLevel2Panel.repaint();
}
});
}
// run all runnables with a full EDT cycle between each one so we get the correct control measurements
class RecurseRunner implements Runnable{
final Iterator<Runnable> toRun;
RecurseRunner(Iterator<Runnable> toRun) {
this.toRun = toRun;
}
@Override
public void run() {
if(toRun.hasNext()){
Runnable runnable = toRun.next();
if(runnable!=null){
runnable.run();
}
containerLevel2Panel.invalidate();
containerLevel2Panel.revalidate();
containerLevel2Panel.repaint();
SwingUtilities.invokeLater(this);
}
}
}
SwingUtilities.invokeLater(new RecurseRunner(runnables.iterator()));
}
}
private void initContainerPanel(final boolean isReinit) {
// don't allow two reinits to be posted at once
if(!isPendingInitContainerPanels){
isPendingInitContainerPanels = true;
SwingUtilities.invokeLater(new InitContainerPanel(isReinit));
}
}
@Override
public Point getWorldBitmapPosition(LatLong ll, int zoom) {
Point2D pnt = BackgroundTileFactorySingleton.getFactory().geoToPixel(new GeoPosition(ll.getLatitude(), ll.getLongitude()), zoom);
return new Point((int) Math.round(pnt.getX()), (int) Math.round(pnt.getY()));
}
@Override
public ComponentControlLauncherApi getControlLauncherApi() {
return componentControlLauncherApi;
}
@Override
public MapDataApi getMapDataApi() {
return new MapDataApi() {
@Override
public ODLTable getGlobalTable(int tableId) {
return globalDs != null ? globalDs.getTableByImmutableId(tableId) : null;
}
@Override
public void runTransactionOnGlobalDatastore(Callable<Boolean> toRun) {
if (globalDs != null) {
TableUtils.runTransaction(globalDs, toRun);
}
}
@Override
public int getLatitudeColumn() {
return DrawableObjectImpl.COL_LATITUDE;
}
@Override
public int getLongitudeColumn() {
return DrawableObjectImpl.COL_LONGITUDE;
}
@Override
public int getGeomColumn() {
return DrawableObjectImpl.COL_GEOMETRY;
}
@Override
public ODLTable getUnfilteredActiveTable() {
return TableUtils.findTable(mapDatastore, PredefinedTags.DRAWABLES);
}
@Override
public ODLTable getUnfilteredInactiveForegroundTable() {
return TableUtils.findTable(mapDatastore, PredefinedTags.DRAWABLES_INACTIVE_FOREGROUND);
}
@Override
public ODLTable getUnfilteredInactiveBackgroundTable() {
return TableUtils.findTable(mapDatastore, PredefinedTags.DRAWABLES_INACTIVE_BACKGROUND);
}
@Override
public int getLegendKeyColumn() {
return DrawableObjectImpl.COL_LEGEND_KEY;
}
@Override
public ODLTableReadOnly getFilteredAllLayersTable(boolean immutableSnapshot) {
// At the moment we always do an immutable snapshot, but we may improve this in the future
return toTable(filtered.allFiltered);
}
@Override
public ODLDatastoreUndoable<? extends ODLTableAlterable> getGlobalDatastore() {
return globalDs;
}
@Override
public ODLTableReadOnly getUnfilteredAllLayersTable(boolean immutableSnapshot) {
// At the moment we always do an immutable snapshot, but we may improve this in the future
// Get table .. this will be a copy as it combines all input tables into one.
// As this recreates a table from the beanmapped objects it is quicker than actually
// reading the original table (as this involves reading many layers of decorators),
// this boosts performance when using the legend.
return toTable(new FilteredTables(objs,mapDatastore, MapApiImpl.this, false).allFiltered);
}
@Override
public int getTooltipColumn() {
return DrawableObjectImpl.COL_TOOLTIP;
}
@Override
public ODLTableReadOnly getActiveTableSelectedOnly() {
long [] sel = (MapApiImpl.this).getSelectedIds();
ODLTable active = getUnfilteredActiveTable();
if(sel!=null && sel.length>0 && active!=null){
ODLApi odlApi = (MapApiImpl.this).getApi();
ODLDatastoreAlterable<? extends ODLTableAlterable> ds = odlApi.tables().createAlterableDs();
odlApi.tables().copyTableDefinition(active, ds);
ODLTable filtered = ds.getTableAt(0);
for(long id : sel){
if(active.containsRowId(id)){
odlApi.tables().copyRowById(active, id, filtered);
}
}
return filtered;
}
return null;
}
@Override
public Iterable<ODLTable> getDrawableTables(ODLDatastore<? extends ODLTable> mapDatastore) {
return new FindDrawableTables(mapDatastore);
}
@Override
public ODLDatastore<? extends ODLTable> getMapDatastore() {
return mapDatastore;
}
@Override
public ODLTableReadOnly getBackgroundImagesTable() {
return TableUtils.findTable(mapDatastore, PredefinedTags.BACKGROUND_IMAGE);
}
};
}
private ODLTableReadOnly toTable(Iterable<? extends DrawableObject> objs) {
BeanTableMappingImpl btm = DrawableObjectImpl.getBeanMapping().getTableMapping(0);
LinkedList<BeanMappedRow> rows = new LinkedList<BeanMappedRow>();
for (DrawableObject o : objs) {
rows.add((BeanMappedRow) o);
}
return btm.writeObjectsToTable(rows.toArray(new BeanMappedRow[rows.size()]));
}
@Override
public <T extends JPanel & Disposable> T getSidePanel(PanelPosition pos) {
return (T) sidePanels[pos.ordinal()];
}
@Override
public void setDefaultMapMode(MapMode mode) {
this.defaultMode = mode;
}
@Override
public MapMode getMapMode() {
return mode;
}
@Override
public MapMode getDefaultMapMode() {
return defaultMode;
}
@Override
public MapToolbar getMapToolbar() {
return toolBar;
}
@Override
public ODLApi getApi() {
return componentControlLauncherApi.getApi();
}
private static class BeanMappedObjects{
private List<TIntObjectHashMap<DrawableObject>> byRow;
BeanMappedObjects() {
byRow = new ArrayList<TIntObjectHashMap<DrawableObject>>(3);
for(int i =0 ; i < 3 ; i++){
byRow.add(new TIntObjectHashMap<DrawableObject>());
}
}
void save(int tableOrder, int row, DrawableObject obj){
byRow.get(tableOrder).put(row, obj);
}
DrawableObject get(int tableOrder, int row){
return byRow.get(tableOrder).get(row);
}
public static BeanMappedObjects create(ODLDatastore<? extends ODLTable> ioDs){
FindDrawableTables finder = new FindDrawableTables(ioDs);
BeanMappedObjects objs = new BeanMappedObjects();
class ToDrawable{
void convert(int tableOrder, ODLTableReadOnly table){
if(table==null){
return;
}
BeanTableMappingImpl btm = DrawableObjectImpl.getBeanMapping().getTableMapping(0);
int n = table.getRowCount();
for (int row = 0; row < n; row++) {
DrawableObject obj = btm.readObjectFromTableByRow(table, row);
if(obj!=null){
objs.save(tableOrder, row, obj);
}
}
}
}
ToDrawable converter = new ToDrawable();
converter.convert(0,finder.background);
converter.convert(1,finder.activeTable);
converter.convert(2,finder.foreground);
return objs;
}
}
public static void registerComponent( ) {
ODLGlobalComponents.register(new AbstractMapViewerComponent() {
@Override
public void execute(final ComponentExecutionApi api, int mode,final Object configuration, ODLDatastore<? extends ODLTable> ioDs, ODLDatastoreAlterable<? extends ODLTableAlterable> outputDs) {
// convert all to drawable objects using the non-EDT datastore on the non-EDT thread
BeanMappedObjects objs = BeanMappedObjects.create(ioDs);
api.submitControlLauncher(new ControlLauncherCallback() {
@Override
public void launchControls(ComponentControlLauncherApi launcherApi) {
DisposableMapPanel panel = (DisposableMapPanel)launcherApi.getRegisteredPanel("Map");
if(panel!=null){
panel.getApi().setObjects(objs,ioDs);
}
else{
// get all plugins
List<MapPlugin> plugins = getPlugins((MapConfig)configuration);
// create the map api
MapApiImpl mapApi = new MapApiImpl(plugins, launcherApi, launcherApi.getGlobalDatastore(),objs, ioDs);
mapApi.connectToGSM(launcherApi.getMapSelectionListRegister());
// register the panel
if (!launcherApi.registerPanel("Map", null, mapApi.getPanel(), true)) {
// presumably UI is unavailable?
mapApi.dispose();
}
}
}
});
}
});
ODLGlobalComponents.register(new ViewLayerStyleComponent());
}
private static List<MapPlugin> getPlugins(MapConfig config) {
List<MapPlugin> plugins = new ArrayList<MapPlugin>();
for(MapPlugin p : GlobalMapPluginManager.getPlugins()){
plugins.add(p);
}
if(config.isUseCustomTooltips()){
plugins.add(new CustomTooltipPlugin());
}else{
plugins.add(new SummariseFieldValuesTooltipPlugin());
}
return plugins;
}
@Override
public void submitWork(Runnable runnable) {
executorService.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
runnable.run();
return null;
}
});
}
@Override
public boolean isDisposed() {
return isDisposed;
}
@Override
public void registerActionFactory(MapActionFactory factory, int priority, String group, boolean needsSetPermission, boolean needsInsertPermission, boolean needsDeletePermission) {
long flags=0;
if(needsSetPermission){
flags |= TableFlags.UI_SET_ALLOWED;
}
if(needsInsertPermission){
flags |= TableFlags.UI_INSERT_ALLOWED;
}
if(needsDeletePermission){
flags |= TableFlags.UI_DELETE_ALLOWED;
}
if(flags!=0){
PluginUtils.registerActionFactory(this, factory, priority, group, flags);
}else{
PluginUtils.registerActionFactory(this, factory, priority, group);
}
}
}