Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* Copyright (C) 2008-2013 LimeTri. All rights reserved.
*
* AgroSense 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.
*
* There are special exceptions to the terms and conditions of the GPLv3 as it
* is applied to this software, see the FLOSS License Exception
* .
*
* AgroSense 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
* AgroSense. If not, see .
*/
package eu.limetri.client.mapviewer.swing;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.beans.DesignMode;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;
import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.painter.AbstractPainter;
import org.jdesktop.swingx.painter.Painter;
import eu.limetri.client.mapviewer.data.AbstractMapViewer;
import eu.limetri.client.mapviewer.data.GeoPosition;
import eu.limetri.client.mapviewer.data.MapViewer;
import eu.limetri.client.mapviewer.data.RepaintCallback;
import eu.limetri.client.mapviewer.data.Tile;
import eu.limetri.client.mapviewer.data.TileFactory;
import eu.limetri.client.mapviewer.data.TileFactoryInfo;
import eu.limetri.client.mapviewer.data.common.TileFactoryInfoSelectionEventHandler;
import eu.limetri.client.mapviewer.data.util.ScaleUtil;
import eu.limetri.client.mapviewer.data.util.ScaleUtil.UnitSystem;
import eu.limetri.client.mapviewer.swing.empty.EmptyTileFactory;
import eu.limetri.client.mapviewer.swing.impl.OfflineTileFactorySwing;
import eu.limetri.client.mapviewer.swing.input.CenterMapListener;
import eu.limetri.client.mapviewer.swing.input.PanKeyListener;
import eu.limetri.client.mapviewer.swing.input.PanMouseInputListener;
import eu.limetri.client.mapviewer.swing.input.ZoomMouseWheelListenerCursor;
/**
* A tile oriented map component that can easily be used with tile sources on
* the web like Google and Yahoo maps, satellite data such as NASA imagery, and
* also with file based sources like pre-processed NASA images. A known map
* provider can be used with the SLMapServerInfo, which will connect to a 2km
* resolution version of NASA's Blue Marble Next Generation imagery. @see
* SLMapServerInfo for more information.
*
* Note, the JXMapViewer has three center point properties. The
* addressLocation property represents an abstract center of the map.
* This would usually be something like the first item in a search result. It is
* a {@link GeoPosition}. The centerPosition property represents the
* current center point of the map. If the user pans the map then the
* centerPosition point will change but the addressLocation will not.
* Calling recenterToAddressLocation() will move the map back to that
* center address. The center property represents the same point as the
* centerPosition property, but as a Point2D in pixel space instead of a
* GeoPosition in lat/long space. Note that the center property is a Point2D in
* the entire world bitmap, not in the portion of the map currently visible. You
* can use the getViewportBounds() method to find the portion of the map
* currently visible and adjust your calculations accordingly. Changing the
* center property will change the centerPosition property and
* vice versa. All three properties are bound.
*
* @author [email protected]
* @see eu.limetri.client.mapviewer.data.bmng.SLMapServerInfo
*/
public class JXMapViewer extends JXPanel implements DesignMode, MapViewer> {
private static final long serialVersionUID = 1L;
public static final int MAX_ZOOM = 17;
public static final int MIN_ZOOM = 1;
public static final String ZOOM = "zoom";
/**
* Indicates whether the component should recenter the map when the "middle"
* mouse button is pressed
*/
private boolean recenterOnClickEnabled = true;
/**
* The overlay to delegate to for painting the "foreground" of the map
* component. This would include painting waypoints, day/night, etc. Also
* receives mouse events.
*/
private Painter overlay;
private boolean designTime;
private Image loadingImage;
private AbstractMapViewer> abstractMapViewer;
private boolean setZoomFlag = false;
private boolean zoomEnabled = false;
private boolean typeSelectionEnabled = false;
private MapTypeSelectionPanelSwing typeSelectionPanel;
// a property change listener which forces repaints when tiles finish
// loading
private TileLoadListener tileLoadListener = new TileLoadListener();
private JPanel rightPanel;
private ZoomPanel zoomPanel;
private Point oldStartPoint;
private Point oldEndPoint;
private CenterMapListener centerMapListener;
/**
* Create a new JXMapViewer. By default it will use the EmptyTileFactory
*/
public JXMapViewer() {
setLayout(new BorderLayout());
initMapViewer();
initInputListeners();
rightPanel = new JPanel(new GridBagLayout());
rightPanel.setOpaque(false);
GridBagConstraints gc = new GridBagConstraints();
gc.gridx = 0;
gc.gridy = 10;
gc.weighty = 1;
gc.weightx = 1;
//TODO replace with miglayout
JPanel fillPanel = new JPanel();
fillPanel.setOpaque(false);
rightPanel.add(fillPanel, gc);
add(rightPanel, BorderLayout.EAST);
abstractMapViewer.setTileFactory(new EmptyTileFactory());
setZoomEnabled(true);
// make a dummy loading image
try {
URL url = this.getClass().getResource(
"mapviewer/resources/loading.png");
this.setLoadingImage(ImageIO.read(url));
} catch (Throwable ex) {
BufferedImage img = new BufferedImage(16, 16,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setColor(Color.black);
g2.fillRect(0, 0, 16, 16);
g2.dispose();
this.setLoadingImage(img);
}
setBackgroundPainter(new AbstractPainter() {
protected void doPaint(Graphics2D g, JXPanel component, int width,
int height) {
doPaintComponent(g);
}
});
abstractMapViewer.addRepaintCallback(new RepaintCallback() {
@Override
public void repaint() {
//TODO right panel is blinking while repainting
rightPanel.repaint();
}
});
}
protected void initInputListeners() {
MouseInputListener mia = new PanMouseInputListener(this);
centerMapListener = new CenterMapListener(this);
this.addMouseListener(mia);
this.addMouseMotionListener(mia);
this.addMouseListener(centerMapListener);
addMouseWheelListener(new ZoomMouseWheelListenerCursor(this));
addKeyListener(new PanKeyListener(this));
}
private void initMapViewer() {
abstractMapViewer = new AbstractMapViewer>() {
@Override
protected void repaint() {
JXMapViewer.this.repaint();
}
@Override
protected int getWidth() {
return JXMapViewer.this.getWidth();
}
@Override
protected Insets getInsets() {
return JXMapViewer.this.getInsets();
}
@Override
protected int getHeight() {
return JXMapViewer.this.getHeight();
}
@Override
protected void firePropertyChange(String propertyName,
Object oldValue, Object newValue) {
JXMapViewer.this.firePropertyChange(propertyName, oldValue,
newValue);
}
};
}
private void doPaintComponent(Graphics g) {
if (!isDesignTime()) {
//correcting bounds of map
Point2D center = abstractMapViewer.getCenter();
int viewerHeight = getHeight();
int maxHeight = (int) (getTileFactory().getMapSize(getZoom()).getHeight() * getTileFactory().getTileSize(getZoom()));
double y = center.getY();
if (y < (viewerHeight/2)) {
y = viewerHeight / 2;
}
if (y > maxHeight - (viewerHeight/2)) {
y = maxHeight - (viewerHeight/2);
}
if (y != center.getY()) {
abstractMapViewer.setCenter(new Point2D.Double(center.getX(), y));
}
//
int zoom = getZoom();
Rectangle viewportBounds = abstractMapViewer.getViewportBounds();
drawMapTiles(g, zoom, viewportBounds);
drawOverlays(zoom, g, viewportBounds);
drawScale(g);
}
super.paintBorder(g);
}
/**
* Indicate that the component is being used at design time, such as in a
* visual editor like NetBeans' Matisse
*
* @param b
* indicates if the component is being used at design time
*/
public void setDesignTime(boolean b) {
this.designTime = b;
}
/**
* Indicates whether the component is being used at design time, such as in
* a visual editor like NetBeans' Matisse
*
* @return boolean indicating if the component is being used at design time
*/
public boolean isDesignTime() {
return designTime;
}
/**
* Draw the map tiles. This method is for implementation use only.
*
* @param g
* Graphics
* @param zoom
* zoom level to draw at
* @param viewportBounds
* the bounds to draw within
*/
protected void drawMapTiles(final Graphics g, final int zoom,
Rectangle viewportBounds) {
int size = getTileFactory().getTileSize(zoom);
Dimension mapSize = getTileFactory().getMapSize(zoom);
// calculate the "visible" viewport area in tiles
int numWide = viewportBounds.width / size + 2;
int numHigh = viewportBounds.height / size + 2;
TileFactoryInfo info = getTileFactory().getInfo();
int tpx = (int) Math.floor(viewportBounds.getX() / info.getTileSize(0));
int tpy = (int) Math.floor(viewportBounds.getY() / info.getTileSize(0));
// fetch the tiles from the factory and store them in the tiles cache
// attach the tileLoadListener
for (int x = 0; x <= numWide; x++) {
for (int y = 0; y <= numHigh; y++) {
int itpx = x + tpx;// topLeftTile.getX();
int itpy = y + tpy;// topLeftTile.getY();
// only proceed if the specified tile point lies within the area
// being painted
if (g.getClipBounds().intersects(new Rectangle(itpx * size - viewportBounds.x, itpy * size - viewportBounds.y, size, size))) {
Tile tile = getTileFactory().getTile(itpx, itpy, zoom);
if (!tile.isLoaded()) {
tile.addUniquePropertyChangeListener("loaded", tileLoadListener); // this is a filthy hack
}
int ox = ((itpx * getTileFactory().getTileSize(zoom)) - viewportBounds.x);
int oy = ((itpy * getTileFactory().getTileSize(zoom)) - viewportBounds.y);
// if the tile is off the map to the north/south, then just
// don't paint anything
if (isTileOnMap(itpx, itpy, mapSize)) {
if (isOpaque()) {
g.setColor(getBackground());
g.fillRect(ox, oy, size, size);
}
} else if (tile.isLoaded()) {
g.drawImage(tile.getImage(), ox, oy, null);
} else {
//show resized image while loading
Tile superTile = getTileFactory().getTile(itpx / 2, itpy / 2, zoom + 1);
if (superTile.isLoaded()) {
int offX = (itpx % 2) * size / 2;
int offY = (itpy % 2) * size / 2;
g.drawImage(superTile.getImage(), ox, oy, ox + size, oy + size, offX, offY, offX + size / 2, offY + size / 2, null);
} else {
int imageX = (getTileFactory().getTileSize(zoom) - getLoadingImage().getWidth(null)) / 2;
int imageY = (getTileFactory().getTileSize(zoom) - getLoadingImage().getHeight(null)) / 2;
g.setColor(Color.GRAY);
g.fillRect(ox, oy, size, size);
g.drawImage(getLoadingImage(), ox + imageX, oy + imageY, null);
}
}
}
}
}
}
private void drawOverlays(final int zoom, final Graphics g,
final Rectangle viewportBounds) {
if (overlay != null) {
overlay.paint((Graphics2D) g, this, getWidth(), getHeight());
}
}
private boolean isTileOnMap(int x, int y, Dimension mapSize) {
return y >= mapSize.getHeight();
}
/**
* Sets the map overlay. This is a Painter which will paint on top of the
* map. It can be used to draw waypoints, lines, or static overlays like
* text messages.
*
* @param overlay
* the map overlay to use
* @see org.jdesktop.swingx.painters.Painter
*/
public void setOverlayPainter(Painter overlay) {
Painter> old = getOverlayPainter();
this.overlay = overlay;
firePropertyChange("mapOverlay", old, getOverlayPainter());
repaint();
}
/**
* Gets the current map overlay
*
* @return the current map overlay
*/
public Painter getOverlayPainter() {
return overlay;
}
/**
* Sets whether the map should recenter itself on mouse clicks (middle mouse
* clicks?)
*
* @param b
* if should recenter
*/
public void setRecenterOnClickEnabled(boolean b) {
boolean old = isRecenterOnClickEnabled();
recenterOnClickEnabled = b;
firePropertyChange("recenterOnClickEnabled", old,
isRecenterOnClickEnabled());
}
/**
* Indicates if the map should recenter itself on mouse clicks.
*
* @return boolean indicating if the map should recenter itself
*/
public boolean isRecenterOnClickEnabled() {
return recenterOnClickEnabled;
}
/**
* A property for an image which will be display when an image is still
* loading.
*
* @return the current property value
*/
public Image getLoadingImage() {
return loadingImage;
}
/**
* A property for an image which will be display when an image is still
* loading.
*
* @param loadingImage
* the new property value
*/
public void setLoadingImage(Image loadingImage) {
this.loadingImage = loadingImage;
}
private final class TileLoadListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
if (Tile.LOADED.equals(evt.getPropertyName()) && Boolean.TRUE.equals(evt.getNewValue())) {
Object source = evt.getSource();
if (source instanceof Tile>) {
Tile> t = (Tile>) evt.getSource();
if (t.getZoom() == getZoom()) {
repaint();
}
}
}
}
}
private void setZoomInternal(int zoom) {
int oldZoom = abstractMapViewer.getZoom();
abstractMapViewer.setZoom(zoom);
setZoomFlag = true;
firePropertyChange(ZOOM, oldZoom, zoom);
setZoomFlag = false;
}
/**
* Set {@link TileFactory}
*
* @param factory
*/
public void setTileFactory(TileFactory> factory) {
TileFactory> oldTileFactory = abstractMapViewer.getTileFactory();
if (oldTileFactory != null) {
oldTileFactory.clearQueueAndStopLoading();
}
abstractMapViewer.setTileFactory(factory);
//TODO clean up tileMap and imageMap
repaint();
}
/**
* Set zoom level
*
* @param zoom
*/
public void setZoom(int zoom) {
setZoomInternal(zoom);
}
/**
* Set {@link GeoPosition} address of the center of the map
*
* @param addressLocation
*/
public void setAddressLocation(GeoPosition addressLocation) {
abstractMapViewer.setAddressLocation(addressLocation);
}
@Override
public TileFactory> getTileFactory() {
return abstractMapViewer.getTileFactory();
}
@Override
public int getZoom() {
return abstractMapViewer.getZoom();
}
@Override
public Rectangle getViewportBounds() {
return abstractMapViewer.getViewportBounds();
}
/**
* Gets center of the map in pixel coordinates
* @return
*/
public Point2D getCenter() {
return abstractMapViewer.getCenter();
}
/**
* Sets the new center of the map in pixel coordinates.
*
* @param center the new center of the map in pixel coordinates
*/
public void setCenter(Point2D center) {
abstractMapViewer.setCenter(center);
}
/**
* Sets center position of the map
*
* @param geoPosition center position of the map
*/
public void setCenterPosition(GeoPosition geoPosition) {
abstractMapViewer.setCenterPosition(geoPosition);
}
/**
* Gets center position of the map
* @return
*/
public GeoPosition getCenterPosition() {
return abstractMapViewer.getCenterPosition();
}
/**
* Gets the current address location of the map. This property does not change when the user
* pans the map. This property is bound.
* @return the current map location (address)
*/
public GeoPosition getAddressLocation() {
return abstractMapViewer.getAddressLocation();
}
public void setRestrictOutsidePanning(boolean restrict) {
abstractMapViewer.setRestrictOutsidePanning(restrict);
}
public void setHorizontalWrapped(boolean wrapped) {
abstractMapViewer.setHorizontalWrapped(wrapped);
}
/**
* Paints zoom selection area rectangle
*
* @param startPoint
* @param endPoint
*/
public void markZoomArea(Point startPoint, Point endPoint) {
Graphics2D g = (Graphics2D) getGraphics();
if (oldStartPoint != null && oldEndPoint != null) {
Rectangle rect = new Rectangle(Math.min(oldStartPoint.x, oldEndPoint.x)-1, Math.min(oldStartPoint.y, oldEndPoint.y)-1, Math.max(oldStartPoint.x, oldEndPoint.x), Math.max(oldStartPoint.y, oldEndPoint.y));
if (rect.x < 0) {
rect.x = 0;
}
if (rect.y < 0) {
rect.y = 0;
}
//TODO zoom components blink and hide part of selection rectangle when those intersect - should be fixed by using different layout manager
g.setClip(rect.x, rect.y, rect.width, rect.height);
doPaintComponent(g);
}
abstractMapViewer.fireRepaintCallbacks();
g.setColor(Color.RED); //TODO LAF
g.setStroke(new BasicStroke(3));
g.drawRect(Math.min(startPoint.x, endPoint.x), Math.min(startPoint.y, endPoint.y), Math.abs(endPoint.x - startPoint.x), Math.abs(endPoint.y - startPoint.y));
oldStartPoint = startPoint;
oldEndPoint = endPoint;
}
/**
* Zoom the map to specified area
*
* @param start
* @param end
*/
public void zoomToArea(Point start, Point end) {
abstractMapViewer.zoomToCoordinates(start.x, start.y, end.x, end.y);
}
public void setZoomEnabled(boolean zoomEnabled) {
if (this.zoomEnabled != zoomEnabled) {
this.zoomEnabled= zoomEnabled;
if (zoomEnabled) {
enableZoom();
} else {
disableZoom();
}
}
}
public boolean isZoomEnabled() {
return zoomEnabled;
}
protected void enableZoom() {
zoomPanel = new ZoomPanel(this);
zoomPanel.attach();
GridBagConstraints gc = new GridBagConstraints();
gc.gridx = 1;
gc.gridy = 1;
gc.weightx = 0;
rightPanel.add(zoomPanel, gc);
}
protected void disableZoom() {
zoomPanel.detach();
rightPanel.remove(zoomPanel);
}
boolean isSetZoomFlag() {
return setZoomFlag;
}
public void setTypeSelectionEnabled(boolean typeSelectionEnabled, TileFactoryInfo...tileFactoryInfos) {
if (this.typeSelectionEnabled != typeSelectionEnabled) {
this.typeSelectionEnabled= typeSelectionEnabled;
if (typeSelectionEnabled) {
enableTypeSelection(tileFactoryInfos);
} else {
disableTypeSelection();
}
}
}
private void disableTypeSelection() {
rightPanel.remove(typeSelectionPanel);
}
private void enableTypeSelection(TileFactoryInfo...tileFactoryInfos) {
typeSelectionPanel = new MapTypeSelectionPanelSwing();
typeSelectionPanel.setItems(tileFactoryInfos);
GridBagConstraints gc = new GridBagConstraints();
gc.gridx = 0;
gc.gridy = 0;
gc.gridwidth = 2;
rightPanel.add(typeSelectionPanel, gc);
typeSelectionPanel.setOnSelectionChange(new TileFactoryInfoSelectionEventHandler() {
@Override
public void itemSelected(TileFactoryInfo tileFactoryInfo) {
setTileFactory(new OfflineTileFactorySwing(tileFactoryInfo));
}
});
typeSelectionPanel.setActiveTileFactoryInfo(getTileFactory().getInfo());
}
public boolean isTypeSelectionEnabled() {
return typeSelectionEnabled;
}
public void setZoomFromGeoPosition(GeoPosition start, GeoPosition end) {
abstractMapViewer.setZoomFromGeoPosition(start, end);
}
public void setDoubleClickZoomEnabled(boolean enabled) {
centerMapListener.setEnabled(enabled);
}
public boolean isDoubleClickZoomEnabled() {
return centerMapListener.isEnabled();
}
private void drawScale(Graphics g) {
long scaleValue = abstractMapViewer.getScaleValue();
int width = (int) (scaleValue * abstractMapViewer.getPixelToMeter() * ScaleUtil.getUnitToMetersRatio());
int xBase = 30;
int yBase = 30;
UnitSystem unitSystem = ScaleUtil.getUnitSystem();
String title = null;
if (UnitSystem.METRIC.equals(unitSystem)) {
if (scaleValue >= 1000) {
title = String.format("%dkm", scaleValue / 1000);
} else {
title = String.format("%dm", scaleValue);
}
} else if (UnitSystem.IMPERIAL.equals(unitSystem)) {
if (scaleValue >= 5280) {
title = String.format("%dmi", scaleValue / 5280);
} else {
title = String.format("%dft", scaleValue);
}
}
int h = getHeight();
g.setColor(Color.WHITE);
g.drawLine(xBase + 4, h-yBase, xBase - 4 + width, h-yBase);
g.drawLine(xBase, h-yBase + 3, xBase + width, h-yBase + 3);
g.drawLine(xBase, h-yBase + 2, xBase, h-yBase - 5);
g.drawLine(xBase, h-yBase - 5, xBase + 3, h- yBase - 5);
g.drawLine(xBase + 3, h-yBase-5, xBase + 3, h-yBase);
g.drawLine(xBase - 3 + width, h-yBase, xBase - 3 + width, h- yBase - 5);
g.drawLine(xBase - 3 + width, h- yBase -5, xBase + width, h- yBase - 5);
g.drawLine(xBase + width, h- yBase -5, xBase + width, h-yBase + 2);
g.setColor(Color.BLACK);
g.drawLine(xBase + 1, h-yBase + 1, xBase - 1 + width, h- yBase + 1);
g.drawLine(xBase + 1, h-yBase + 2, xBase - 1 + width, h-yBase + 2);
g.drawLine(xBase + 1, h-yBase + 1, xBase + 1, h-yBase - 4);
g.drawLine(xBase + 2, h-yBase + 1, xBase + 2, h-yBase-4);
g.drawLine(xBase - 1 + width, h-yBase+1, xBase - 1 + width, h-yBase-4);
g.drawLine(xBase - 2 + width, h-yBase+1, xBase - 2 + width, h-yBase-4);
g.setColor(Color.BLACK);
g.drawString(title, xBase + 10 , h-yBase-5);
}
public GeoPosition[] getScreenCoordinates() {
int width = getWidth();
int height = getHeight();
Point2D.Double point = new Point2D.Double(0, 0);
GeoPosition positionNW = abstractMapViewer.convertPointToGeoPosition(point);
point = new Point2D.Double(width, 0);
GeoPosition positionNE = abstractMapViewer.convertPointToGeoPosition(point);
point = new Point2D.Double(width, height);
GeoPosition positionSE = abstractMapViewer.convertPointToGeoPosition(point);
point = new Point2D.Double(0, height);
GeoPosition positionSW = abstractMapViewer.convertPointToGeoPosition(point);
GeoPosition[] cornerPositions = new GeoPosition[]{positionNW, positionNE, positionSE, positionSW, positionNW};
return cornerPositions;
}
@Override
public void addNotify() {
super.addNotify();
MapViewerLookup.getInstance().addObject(this);
}
@Override
public void removeNotify() {
super.removeNotify();
MapViewerLookup.getInstance().removeObject(this);
}
public Point2D convertGeoPositionToPoint(GeoPosition pos) {
return abstractMapViewer.convertGeoPositionToPoint(pos);
}
public GeoPosition convertPointToGeoPosition(Point2D pt) {
return abstractMapViewer.convertPointToGeoPosition(pt);
}
}