--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.simantics.district.maps</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Raster Mapping
+Bundle-SymbolicName: org.simantics.district.maps
+Bundle-Version: 1.0.0.qualifier
+Bundle-Vendor: VTT Technical Research Centre of Finland
+Require-Bundle: org.simantics.scenegraph,
+ org.eclipse.core.runtime;bundle-version="3.5.0";resolution:=optional,
+ org.eclipse.core.net;bundle-version="1.2.1";resolution:=optional,
+ org.simantics.g2d;bundle-version="0.9.4",
+ org.simantics.utils.datastructures;bundle-version="1.0.0"
+Export-Package: org.simantics.maps,
+ org.simantics.maps.debug,
+ org.simantics.maps.eclipse,
+ org.simantics.maps.sg,
+ org.simantics.maps.tile,
+ org.simantics.maps.wms
+Bundle-ClassPath: .
+Import-Package: org.simantics.scenegraph.g2d
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6,
+ JavaSE-1.7
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+<?xml version="1.0"?>
+<project name="org.simantics.maps" basedir="." default="jar">
+ <property name="src" value="src"/>
+ <property name="output" value="classes"/>
+ <property name="lib" value="lib"/>
+ <property name="sg" value="../org.simantics.scenegraph/"/>
+ <property environment="env"/>
+ <property name="compiler" value="${env.JAVA_HOME}/bin/javac"/>
+ <property name="jrelib" value="${env.JAVA_HOME}/jre/lib"/>
+
+ <path id="compile.classpath">
+ <fileset dir="${sg}">
+ <include name="*.jar"/>
+ </fileset>
+ </path>
+
+ <target name="compile" depends="create">
+ <javac destdir="${output}" sourcepath="" source="1.6" target="1.6">
+ <src path="${src}"/>
+ <include name="fi/vtt/simantics/map/**"/>
+ <exclude name="fi/vtt/simantics/map/eclipse/**"/>
+ <classpath refid="compile.classpath"/>
+ </javac>
+ </target>
+
+ <target name="jar" depends="compile">
+ <jar destfile="org.simantics.maps.jar">
+ <fileset dir="${output}"/>
+ </jar>
+ </target>
+
+ <target name="clean">
+ <delete dir="${output}"/>
+ <delete>
+ <fileset dir="." includes="*.jar"/>
+ </delete>
+ </target>
+
+ <target name="create">
+ <mkdir dir="${output}"/>
+ </target>
+</project>
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps;
+
+import java.net.Proxy;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public interface IProxyUtil {
+
+ public Proxy getProxyService(final String url);
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps;
+
+/**
+ * @author Toni Kalajainen
+ */
+public class ProvisionException extends RuntimeException {
+
+ private static final long serialVersionUID = 1402574341473095723L;
+
+ public ProvisionException() {
+ super();
+ }
+
+ public ProvisionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ProvisionException(String message) {
+ super(message);
+ }
+
+ public ProvisionException(Throwable cause) {
+ super(cause);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Properties;
+
+import org.simantics.maps.pojo.AppletProxyUtil;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class WebService {
+
+ protected URI uri;
+ protected URL service;
+
+ protected HttpURLConnection connection;
+
+ public WebService(String address) throws URISyntaxException, MalformedURLException {
+ this.uri = new URI(address);
+ this.service = uri.toURL();
+ }
+
+ /**
+ * Eclipse has different proxy configuration than POJO applications have,
+ * and Eclipse proxy code cannot be included in POJO applications.
+ *
+ * @return
+ */
+ private IProxyUtil getProxyUtil() {
+ IProxyUtil util = null;
+ try {
+ Class<?> proxyClass = (Class<?>) Class.forName("org.simantics.maps.eclipse.EclipseProxyUtil");
+ util = (IProxyUtil)proxyClass.newInstance();
+ } catch (ClassNotFoundException e1) {
+ } catch (InstantiationException e) {
+ } catch (IllegalAccessException e) {
+ }
+ // AppletProxyUtil should exist always..
+ if(util == null) {
+ util = new AppletProxyUtil();
+ }
+ return util;
+ }
+
+ public URI getURI() {
+ return uri;
+ }
+
+ public HttpURLConnection openConnection(String file, String query) throws MalformedURLException, IOException {
+ HttpURLConnection connection = null;
+
+ String suffix = query != null ? "?"+query : "";
+ URL url = new URL(service.getProtocol(), service.getHost(), service.getPort(), service.getFile() + file + suffix);
+
+ Properties systemProperties = System.getProperties();
+ Proxy proxy = getProxyUtil().getProxyService(url.toString());
+ if(proxy != null && ((InetSocketAddress)proxy.address()) != null) {
+ systemProperties.setProperty("http.proxyHost", ((InetSocketAddress)proxy.address()).getHostName());
+ systemProperties.setProperty("http.proxyPort", ""+((InetSocketAddress)proxy.address()).getPort());
+ }
+
+ URLConnection con = url.openConnection();
+ if(con instanceof HttpURLConnection) {
+ connection = (HttpURLConnection)con;
+ connection.setConnectTimeout(10000);
+ connection.connect();
+ }
+ return connection;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.debug;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.RenderingHints;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.simantics.maps.ProvisionException;
+import org.simantics.maps.tile.ITileProvider;
+import org.simantics.maps.tile.TileKey;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class DebugTileProvider implements ITileProvider {
+
+ private final Rectangle2D EXTENT = new Rectangle2D.Double(-180, -90, 360, 180);
+
+ private int tileSize;
+
+ private URI source;
+
+ public DebugTileProvider(int tileSize) {
+ this.tileSize = tileSize;
+
+ try {
+ this.source = new URI("debug://localhost");
+ } catch (URISyntaxException e) {
+ throw new Error("Should never happen", e);
+ }
+ }
+
+ @Override
+ public URI getSource() {
+ return source;
+ }
+
+ @Override
+ public Rectangle2D getExtent() {
+ return (Rectangle2D) EXTENT.clone();
+ }
+
+ @Override
+ public Image get(TileKey key) throws ProvisionException {
+ int level = key.getLevel();
+ int x = key.getX();
+ int y = key.getY();
+
+ double xTiles = Math.pow(2, level + 1);
+ double yTiles = Math.pow(2, level);
+
+ if (level < 0)
+ throw new IllegalArgumentException("invalid tile level " + level + " (tile=" + key +")");
+ if (x < 0 || x >= (int) xTiles)
+ throw new IllegalArgumentException("tile x out of bounds " + x + " (tile=" + key +")");
+ if (y < 0 || y >= (int) yTiles)
+ throw new IllegalArgumentException("tile y out of bounds " + y + " (tile=" + key +")");
+
+ Rectangle2D r = new Rectangle2D.Double();
+
+ double minx = -180;
+ double miny = -90;
+ double w = 360;
+ double h = 180;
+
+ double xdelta = w / xTiles;
+ double ydelta = h / yTiles;
+ double xx = x;
+ double yy = yTiles - y - 1;
+
+ r.setFrame(
+ minx + xdelta*xx,
+ miny + ydelta*yy,
+ xdelta,
+ ydelta
+ );
+// System.out.println(yTiles + ", " + key.y + " => " + r);
+
+ BufferedImage image = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_3BYTE_BGR);
+ Graphics2D g = image.createGraphics();
+ try {
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+ g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 32));
+
+ g.setColor(Color.BLACK);
+ g.fillRect(0, 0, tileSize, tileSize);
+ g.setColor(Color.LIGHT_GRAY);
+ g.fillRect(1, 1, tileSize - 1, tileSize - 1);
+ g.setColor(Color.BLACK);
+
+ drawCenteredString(g, tileSize + "x" + tileSize, tileSize, tileSize / 2 - 50);
+ drawCenteredString(g, "(" + x + ", " + y + ")(" + level + ")", tileSize, tileSize / 2 + 50);
+
+ drawCenteredString(g, r.getMaxY() + "", tileSize, 50);
+ drawCenteredString(g, r.getMinY() + "", tileSize, 500);
+ g.drawString(r.getMinX() + "", 20, tileSize / 2);
+ g.drawString(r.getMaxX() + "", 412, tileSize / 2);
+
+ // Simulate network delays
+ try { Thread.sleep(100); } catch (InterruptedException e) {}
+
+ return image;
+ } finally {
+ g.dispose();
+ }
+ }
+
+ private void drawCenteredString(Graphics2D g, String str, int width, int y) {
+ FontMetrics metrics = g.getFontMetrics();
+ Rectangle2D r2d = metrics.getStringBounds(str, g);
+ g.drawString(str, (int)(width / 2 - r2d.getWidth() / 2.0), y);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.eclipse;
+
+import java.awt.Image;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+
+import javax.imageio.ImageIO;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+import org.osgi.framework.Bundle;
+import org.simantics.maps.ProvisionException;
+import org.simantics.maps.tile.ITileProvider;
+import org.simantics.maps.tile.TileKey;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class DiskCachingTileProvider implements ITileProvider {
+
+ private final String CACHED_IMAGE_FORMAT = "png";
+
+ ITileProvider provider;
+
+ IPath cachePath;
+
+ boolean supportAlpha;
+
+// private ImageWriter writer;
+//
+// private ImageReader reader;
+//
+// private ImageTypeSpecifier noAlphaType = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
+//
+// private ImageTypeSpecifier alphaType = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
+
+
+ public DiskCachingTileProvider(ITileProvider provider, Boolean supportAlpha) {
+ this.provider = provider;
+ this.supportAlpha = supportAlpha;
+ initCache();
+ }
+
+ @Override
+ public URI getSource() {
+ return provider.getSource();
+ }
+
+ @Override
+ public Rectangle2D getExtent() {
+ return provider.getExtent();
+ }
+
+ private void initCache() {
+ Bundle b = Platform.getBundle("org.simantics.maps");
+ if (b == null)
+ return;
+ IPath statePath = Platform.getStateLocation(b);
+ URI source = provider.getSource();
+ IPath cache = statePath.append("cache").append(source.getScheme()).append(source.getHost());
+
+ // Create the cache directory if it doesn't exist.
+ File f = cache.toFile();
+ if (!f.exists()) {
+ f.mkdirs();
+ } else if (!f.isDirectory()) {
+// FIXME
+// ErrorLogger.defaultLogError("Cache directory " + f.toString() + " already exists as a file, cannot use it for cache", new Exception());
+ return;
+ }
+
+ this.cachePath = cache;
+// System.out.println("MAP DATA CACHE: " + cachePath);
+
+// Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(CACHED_IMAGE_FORMAT);
+// writer = writers.next();
+// Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(CACHED_IMAGE_FORMAT);
+// reader = readers.next();
+ }
+
+ private IPath toTileCachePath(TileKey key) {
+ return cachePath.append(key.getLevel() + "." + key.getX() + "." + key.getY());
+ }
+
+ private Image lookupFromDisk(TileKey key) {
+ if (cachePath == null)
+ return null;
+
+ IPath cachePath = toTileCachePath(key).addFileExtension(CACHED_IMAGE_FORMAT);
+ File f = cachePath.toFile();
+ if (f.exists()) {
+// ImageInputStream iis = null;
+ try {
+// iis = ImageIO.createImageInputStream(f);
+// ImageReadParam param = reader.getDefaultReadParam();
+// param.setDestinationType(supportAlpha ? alphaType : noAlphaType);
+// reader.setInput(iis, true, true);
+// BufferedImage img = reader.read(0, param);
+ BufferedImage img = ImageIO.read(f);
+ if (img != null) {
+ //System.out.println("[" + provider + "] Disk cache hit: " + key);
+ //System.out.println("[" + provider + "] image: " + img);
+ // Need to make sure that the type of the loaded image is
+ // something that can be rendered in an accelerated fashion.
+ if (img.getType() != BufferedImage.TYPE_CUSTOM)
+ return img;
+
+ BufferedImage img2 = null;
+ if (img.getAlphaRaster() != null) {
+ img2 = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
+ } else {
+ img2 = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
+ }
+ //System.out.println("[" + provider + "] image2: " + img2);
+
+ img.copyData(img2.getRaster());
+ return img2;
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+// try {
+// iis.close();
+// } catch (IOException e) {
+// }
+ }
+ // Data is most likely corrupted, just destroy it.
+ f.delete();
+ }
+// System.out.println("Disk cache miss: " + key);
+ return null;
+ }
+
+ private void cacheToDisk(TileKey key, Image img) {
+ if (cachePath == null)
+ return;
+
+ if (!(img instanceof RenderedImage))
+ return;
+
+ IPath cachePath = toTileCachePath(key).addFileExtension(CACHED_IMAGE_FORMAT);
+ File f = cachePath.toFile();
+ try {
+ if (ImageIO.write((RenderedImage) img, CACHED_IMAGE_FORMAT, f)) {
+ //System.out.println("[" + provider + "] Disk cache write: " + key);
+ }
+ } catch (IOException e) {
+ // Caching failed, be silent about it.
+ }
+ }
+
+ @Override
+ public Image get(final TileKey key) throws ProvisionException {
+ Image img = lookupFromDisk(key);
+ if (img == null) {
+ img = provider.get(key);
+ final Image image = img;
+
+ // Write image to disk in the background
+ // TODO: use somekind of worker executor system instead of just threads.
+ new Thread() {
+ @Override
+ public void run() {
+ cacheToDisk(key, image);
+ }
+ }.start();
+ }
+ return img;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " [" + provider.toString() + "]";
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.eclipse;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+
+import org.eclipse.core.net.proxy.IProxyData;
+import org.eclipse.core.net.proxy.IProxyService;
+import org.eclipse.core.runtime.Platform;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.simantics.maps.IProxyUtil;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class EclipseProxyUtil implements IProxyUtil {
+
+ /**
+ * @return IProxyService of the platform or <code>null</code> if the
+ * service is not available.
+ */
+ public Proxy getProxyService(final String url) {
+ Bundle b = Platform.getBundle("org.eclipse.core.net");
+ if (b == null)
+ return null;
+
+ BundleContext bc = b.getBundleContext();
+ ServiceReference<IProxyService> ref = bc.getServiceReference(IProxyService.class);
+ if (ref == null)
+ return null;
+
+ IProxyService service = bc.getService(ref);
+ if (service != null && service.isProxiesEnabled()) {
+ IProxyData proxyData = service.getProxyData(IProxyData.HTTP_PROXY_TYPE);
+ if (proxyData != null && proxyData.getHost() != null) {
+ return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyData.getHost(), proxyData.getPort()));
+ }
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.eclipse;
+
+import org.simantics.g2d.canvas.Hints;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
+import org.simantics.maps.sg.MapNode;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;
+import org.simantics.scenegraph.g2d.events.command.Commands;
+import org.simantics.utils.datastructures.hints.HintListenerAdapter;
+import org.simantics.utils.datastructures.hints.IHintListener;
+import org.simantics.utils.datastructures.hints.IHintObservable;
+import org.simantics.utils.datastructures.hints.IHintContext.Key;
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
+
+/**
+ * MapPainter is an ICanvasContext participant that uses the scene graph
+ * {@link MapNode} to draw tiled maps in the background of the canvas.
+ *
+ * @author J-P Laine
+ *
+ * @see MapNode
+ */
+public class MapPainter extends AbstractCanvasParticipant {
+
+ /**
+ * Grid enabled status. Default value is True
+ */
+ public static final Key KEY_MAP_ENABLED = new KeyOf(Boolean.class);
+
+ public static final double ZOOM_IN_LIMIT = 10000000.0;
+
+ public static final double ZOOM_OUT_LIMIT = 10.0;
+
+ IHintListener mapListener = new HintListenerAdapter() {
+ public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
+ ICanvasContext cc = getContext();
+ if (cc != null) {
+ updateNode();
+ cc.getContentContext().setDirty();
+ }
+ }
+ };
+
+ protected MapNode node = null;
+
+ private int scale;
+
+ public MapPainter(int scale) {
+ this.scale = scale;
+ }
+
+ @Override
+ public void addedToContext(ICanvasContext ctx) {
+ super.addedToContext(ctx);
+ getHintStack().addKeyHintListener(getThread(), KEY_MAP_ENABLED, mapListener);
+ }
+
+ @Override
+ public void removedFromContext(ICanvasContext ctx) {
+ getHintStack().removeKeyHintListener(getThread(), KEY_MAP_ENABLED, mapListener);
+ super.removedFromContext(ctx);
+ }
+
+ @EventHandler(priority = 0)
+ public boolean handleKeyEvent(CommandEvent e) {
+ if (e.command.equals( Commands.MAP_ENABLE )) {
+ setEnabled(true);
+ updateNode();
+ setDirty();
+ return true;
+ } else if (e.command.equals( Commands.MAP_DISABLE )) {
+ setEnabled(false);
+ updateNode();
+ setDirty();
+ return true;
+ } else if (e.command.equals( Commands.MAP_TOGGLE )) {
+ setEnabled(!isMapEnabled());
+ updateNode();
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+ @SGInit
+ public void initSG(G2DParentNode parent) {
+ node = parent.addNode("map", MapNode.class);
+ node.setScale(scale);
+ node.setEnabled(true);
+ node.setZIndex(Integer.MIN_VALUE + 999); // Just under the grid
+ }
+
+ @SGCleanup
+ public void cleanupSG() {
+ node.remove();
+ }
+
+ protected void updateNode() {
+ node.setEnabled(isPaintingEnabled());
+ }
+
+ boolean isPaintingEnabled() {
+ boolean enabled = isMapEnabled();
+ Boolean globalDisable = getHint(Hints.KEY_DISABLE_PAINTING);
+ return enabled && !Boolean.TRUE.equals(globalDisable);
+ }
+
+ public boolean isMapEnabled() {
+ Boolean enabled = getHint(KEY_MAP_ENABLED);
+ return !Boolean.FALSE.equals(enabled);
+ }
+
+ public void setEnabled(boolean enabled) {
+ setHint(KEY_MAP_ENABLED, enabled);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.eclipse;
+
+import java.awt.Image;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.simantics.maps.query.Query;
+import org.simantics.maps.tile.ITileProvider;
+import org.simantics.maps.tile.TileKey;
+
+/**
+ * @author Tuukka Lehtonen
+ * @see TileJobQueue
+ */
+public class TileJob extends Job {
+
+ private ITileProvider provider;
+ private Query<TileKey, Image> query;
+
+ public TileJob(Query<TileKey, Image> query, ITileProvider provider) {
+ super("Raster Map Request Job");
+ assert(query != null);
+ this.query = query;
+ this.provider = provider;
+ }
+
+ public Query<TileKey, Image> getQuery() {
+ return query;
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ setThread(Thread.currentThread());
+ monitor.beginTask("Querying map data", 1);
+ try {
+ // Check if canceled
+ if (monitor.isCanceled()) {
+ cancel();
+ return Status.CANCEL_STATUS;
+ }
+ // Query
+ try {
+ monitor.subTask(query.source.toString());
+ Image result = provider.get(query.source);
+ query.listener.queryComplete(query, result);
+ } catch (Exception e) {
+ query.listener.queryFailed(query, e);
+ }
+ monitor.worked(1);
+ monitor.subTask("");
+ } finally {
+ monitor.done();
+ }
+ return Status.OK_STATUS;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.eclipse;
+
+import java.awt.Image;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+import org.simantics.maps.query.IQueryListener;
+import org.simantics.maps.query.Query;
+import org.simantics.maps.tile.IFilter;
+import org.simantics.maps.tile.ITileJobQueue;
+import org.simantics.maps.tile.ITileListener;
+import org.simantics.maps.tile.ITileProvider;
+import org.simantics.maps.tile.TileKey;
+
+/**
+ * @author Tuukka Lehtonen
+ * @see TileJob
+ */
+public class TileJobQueue implements ITileJobQueue {
+
+ private ITileProvider provider;
+
+ private Map<TileKey, TileJob> queries = new Hashtable<TileKey, TileJob>();
+
+ public TileJobQueue() {
+ }
+
+ public void setTileProvider(ITileProvider provider) {
+ this.provider = provider;
+ }
+
+ public void addJob(Query<TileKey, Image> job) {
+ TileJob tj = new TileJob(job, provider);
+ queries.put(job.source, tj);
+ tj.schedule();
+ }
+
+ public void addAsFirstJob(Query<TileKey, Image> job) {
+ TileJob tj = new TileJob(job, provider);
+ queries.put(job.source, tj);
+ tj.schedule();
+ }
+
+ public void removeJob(Query<TileKey, Image> job) {
+ TileJob t = queries.remove(job.source);
+ if(t != null) {
+ t.cancel();
+ }
+ }
+
+ @Override
+ public void addJob(final TileKey key, final ITileListener listener) {
+ addJob(new Query<TileKey, Image>(key, new IQueryListener<TileKey, Image>() {
+ @Override
+ public void queryCanceled(Query<TileKey, Image> job) {
+ listener.tileCanceled(key);
+ }
+ @Override
+ public void queryFailed(Query<TileKey, Image> job, Exception error) {
+ listener.tileFailed(key, error);
+ }
+ @Override
+ public void queryComplete(Query<TileKey, Image> job, Image result) {
+ listener.tileUpdated(key, result);
+ }
+ }));
+ }
+
+ @Override
+ public void filterQueries(IFilter<TileKey> filter) {
+ Set<TileKey> keys = new HashSet<TileKey>(queries.keySet());
+ for(TileKey key : keys) {
+ if (!filter.select(key)) {
+ TileJob t = queries.remove(key);
+ if(t != null) {
+ t.cancel();
+ t.getQuery().listener.queryCanceled(t.getQuery());
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.osm;
+
+import java.awt.geom.Rectangle2D;
+import java.net.MalformedURLException;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class OSMGetMapQuery {
+
+ String srs = "EPSG:4326";
+
+ int width;
+
+ int height;
+
+ Rectangle2D bbox;
+
+ String[] styles;
+
+ String[] layers;
+
+ String format;
+
+ public OSMGetMapQuery(int width, int height, Rectangle2D bbox,
+ String format, String... layers) throws MalformedURLException {
+ this.width = width;
+ this.height = height;
+ this.bbox = bbox;
+ this.format = format;
+ this.layers = layers;
+ }
+
+ public void setSpatialReferenceSystem(String srs) {
+ this.srs = srs;
+ }
+
+ public void setStyles(String... styles) {
+ this.styles = styles;
+ }
+
+ public void setLayers(String... layers) {
+ this.layers = layers;
+ }
+
+ public static String getTileNumber(final double lat, final double lon,
+ final int zoom) {
+ int xtile = (int) Math.floor((lon + 180) / 360 * (1 << zoom));
+ int ytile = (int) Math.floor((1 - Math.log(
+ Math.tan(Math.toRadians(lat))
+ + 1 / Math.cos(Math.toRadians(lat)))
+ / Math.PI)
+ / 2 * (1 << zoom));
+ return ("" + zoom + "/" + xtile + "/" + ytile+".png");
+ }
+
+ public String toString() {
+ return getTileNumber(bbox.getMinX(), bbox.getMinY(), 16);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.osm;
+
+import java.awt.Dimension;
+import java.awt.Image;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+
+import org.simantics.maps.ProvisionException;
+import org.simantics.maps.WebService;
+import org.simantics.maps.tile.ITileProvider;
+import org.simantics.maps.tile.TileKey;
+import org.simantics.maps.wms.ServiceException;
+
+/**
+ * @author Tuukka Lehtonen
+ * @see OSMGetMapQuery
+ */
+public class OSMTileProvider implements ITileProvider {
+
+ private WebService service;
+
+ private int tileSize;
+
+ public OSMTileProvider(WebService service, int tileSize) {
+ this.service = service;
+ this.tileSize = tileSize;
+ }
+
+ @Override
+ public URI getSource() {
+ return service.getURI();
+ }
+
+ @Override
+ public Rectangle2D getExtent() {
+ return new Rectangle2D.Double(-180, -90, 360, 180);
+ }
+
+ @Override
+ public Image get(TileKey key) throws ProvisionException {
+ int level = key.getLevel();
+ int x = key.getX();
+ int y = key.getY();
+
+ double xTiles = Math.pow(2, level + 1);
+ double yTiles = Math.pow(2, level);
+
+ if (level < 0)
+ throw new IllegalArgumentException("invalid tile level " + level + " (tile=" + key +")");
+ if (x < 0 || x >= (int) xTiles)
+ throw new IllegalArgumentException("tile x out of bounds " + x + " (tile=" + key +")");
+ if (y < 0 || y >= (int) yTiles)
+ throw new IllegalArgumentException("tile y out of bounds " + y + " (tile=" + key +")");
+
+ try {
+ Rectangle2D r = new Rectangle2D.Double(x, y, 10, 10);
+
+// System.out.println("TileProvider: requesting " + key + ", bbox=" + r + ", size=" + tileSize);
+ BufferedImage img = getMap(service, tileSize, tileSize, r, "image/png", level);//layers);
+// System.out.println("TileProvider: response: " + img);
+ return img;
+ } catch (MalformedURLException e) {
+ throw new ProvisionException(e);
+ } catch (IOException e) {
+ throw new ProvisionException(e);
+ }
+ }
+
+
+ /**
+ * Get a bufferedimage of the map data for the geographical coordinates.
+ *
+ * @param x
+ * @param y
+ * @param z
+ * @return
+ * @throws MalformedURLException
+ */
+ public static BufferedImage getMap(WebService service, Dimension rasterSize, Rectangle2D bbox, String format, int level) throws MalformedURLException, IOException {
+ return getMap(service, rasterSize.width, rasterSize.height, bbox, format, level);
+ }
+
+ /**
+ * Get a bufferedimage of the map data for the geographical coordinates.
+ *
+ * @param x
+ * @param y
+ * @param z
+ * @return
+ * @throws MalformedURLException
+ */
+ public static BufferedImage getMap(WebService service, int rasterWidth, int rasterHeight, Rectangle2D bbox, String format, int level) throws MalformedURLException, IOException {
+ if (rasterWidth == 0 || rasterHeight == 0)
+ return new BufferedImage(0, 0, BufferedImage.TYPE_INT_ARGB);
+
+ HttpURLConnection connection = null;
+ try {
+ connection = service.openConnection(("" + level + "/" + Math.round(bbox.getMinX()) + "/" + Math.round(bbox.getMinY())+".png"), null);
+
+ // Execute the method.
+ int statusCode = connection.getResponseCode();
+
+ if (statusCode != HttpURLConnection.HTTP_OK) {
+ System.err.println("Method failed: " + connection.getResponseMessage());
+ }
+
+// for (Header h : method.getResponseHeaders()) {
+// System.out.println("HDR: " + h.getName() + ": " + h.getValue());
+// }
+
+ String contentType = connection.getHeaderField("Content-Type");
+ if (contentType != null) {
+// System.out.println("Content-Type: " + contentType);
+ if (contentType.equals(format)) {
+ InputStream response = connection.getInputStream();
+ BufferedImage img = ImageIO.read(response);
+ if (img == null) {
+ throw new IIOException("ImageIO returned null, unable to decode stream as image data.");
+ }
+ return img;
+ }
+ }
+
+ // Request failed, produce as much debug information as possible.
+ StringBuilder sb = new StringBuilder();
+ sb.append("Response headers:\n");
+ for (String h : connection.getHeaderFields().keySet()) {
+ sb.append(h);
+ sb.append(": ");
+ sb.append(connection.getHeaderField(h));
+ sb.append("\n");
+ }
+ String response = connection.getContent().toString();
+ sb.append("Response body:\n");
+ sb.append(response);
+ throw new ServiceException("WMS GetMap request failed: " + connection.getURL().toString() + "\n" + sb.toString());
+ } finally {
+ // Release the connection.
+ if (connection != null)
+ connection.disconnect();
+ }
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.pojo;
+
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import org.simantics.maps.IProxyUtil;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class AppletProxyUtil implements IProxyUtil {
+
+ /**
+ * @return IProxyService of the platform or <code>null</code> if the
+ * service is not available.
+ */
+ public Proxy getProxyService(final String url) {
+ List<Proxy> l;
+ try {
+ l = ProxySelector.getDefault().select(new URI(url));
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ for(Proxy proxy : l) {
+ if(proxy.type() == Proxy.Type.HTTP && proxy.address() != null) {
+ return proxy;
+ }
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.pojo;
+
+import java.awt.Image;
+import java.util.LinkedList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+import org.simantics.maps.ProvisionException;
+import org.simantics.maps.query.Query;
+import org.simantics.maps.tile.IFilter;
+import org.simantics.maps.tile.ITileProvider;
+import org.simantics.maps.tile.TileKey;
+
+/**
+ * @author Tuukka Lehtonen
+ * @see TileJobQueue
+ */
+public class TileJob implements Runnable {
+
+ LinkedList<Query<TileKey, Image>> queue = new LinkedList<Query<TileKey, Image>>();
+ Executor executor = new ScheduledThreadPoolExecutor(1);
+ ITileProvider provider;
+
+ public TileJob() {
+ }
+
+ public void setTileProvider(ITileProvider provider) {
+ this.provider = provider;
+ }
+
+ protected Image doQuery(TileKey key) throws ProvisionException {
+ return provider.get(key);
+ }
+
+ public void run() {
+ Query<TileKey, Image> job = pullNextJob();
+ do {
+ if (job != null) {
+ try {
+ Image result = doQuery(job.source);
+ job.listener.queryComplete(job, result);
+ } catch (Exception e) {
+ job.listener.queryFailed(job, e);
+ }
+ }
+ // 4. Pick next job
+ job = pullNextJob();
+ } while (job != null);
+ }
+
+ protected synchronized int jobsLeft() {
+ return queue.size();
+ }
+
+ protected synchronized Query<TileKey, Image> pullNextJob() {
+ if (queue.isEmpty())
+ return null;
+ return queue.removeFirst();
+ }
+
+ public synchronized void clear() {
+ @SuppressWarnings("unchecked")
+ Query<TileKey, Image> jobs[] = queue.toArray(new Query[queue.size()]);
+ for (Query<TileKey, Image> j : jobs)
+ removeJob(j);
+ }
+
+ public synchronized void addJob(Query<TileKey, Image> job) {
+ queue.addLast(job);
+ if (queue.size() == 1) {
+ executor.execute(this);
+ }
+ }
+
+ public synchronized void addAsFirstJob(Query<TileKey, Image> job) {
+ queue.addFirst(job);
+ if (queue.size() == 1) {
+ executor.execute(this);
+ }
+ }
+
+ public synchronized boolean removeJob(Query<TileKey, Image> job) {
+ if (queue.remove(job)) {
+ job.listener.queryCanceled(job);
+ return true;
+ }
+ return false;
+ }
+
+ public synchronized void filterQueries(IFilter<TileKey> filter) {
+ if (queue.isEmpty())
+ return;
+
+ LinkedList<Query<TileKey, Image>> result = new LinkedList<Query<TileKey, Image>>();
+ for (Query<TileKey, Image> query : queue) {
+ if (filter.select(query.source))
+ result.add(query);
+ else
+ query.listener.queryCanceled(query);
+ }
+ this.queue = result;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.pojo;
+
+import java.awt.Image;
+
+import org.simantics.maps.query.IQueryListener;
+import org.simantics.maps.query.Query;
+import org.simantics.maps.tile.IFilter;
+import org.simantics.maps.tile.ITileJobQueue;
+import org.simantics.maps.tile.ITileListener;
+import org.simantics.maps.tile.ITileProvider;
+import org.simantics.maps.tile.TileKey;
+
+/**
+ * @author Tuukka Lehtonen
+ * @see TileJob
+ */
+public class TileJobQueue implements ITileJobQueue {
+
+ private TileJob job;
+
+ public TileJobQueue() {
+ }
+
+ public void setTileProvider(ITileProvider provider) {
+ getJob().setTileProvider(provider);
+ }
+
+ public synchronized TileJob getJob() {
+ if (job == null)
+ job = new TileJob();
+ return job;
+ }
+
+ @Override
+ public void addJob(final TileKey key, final ITileListener listener) {
+ getJob().addJob(new Query<TileKey, Image>(key, new IQueryListener<TileKey, Image>() {
+ @Override
+ public void queryCanceled(Query<TileKey, Image> job) {
+ listener.tileCanceled(key);
+ }
+ @Override
+ public void queryFailed(Query<TileKey, Image> job, Exception error) {
+ listener.tileFailed(key, error);
+ }
+ @Override
+ public void queryComplete(Query<TileKey, Image> job, Image result) {
+ listener.tileUpdated(key, result);
+ }
+ }));
+ }
+
+ @Override
+ public void filterQueries(IFilter<TileKey> filter) {
+ getJob().filterQueries(filter);
+ }
+
+ public void addAsFirstJob(Query<TileKey, Image> job) {
+ getJob().addAsFirstJob(job);
+ }
+
+ public void removeJob(Query<TileKey, Image> job) {
+ getJob().removeJob(job);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2007 VTT Technical Research Centre of Finland and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.query;
+
+/**
+ * @author Tuukka Lehtonen
+ *
+ * @param <S>
+ * @param <R>
+ */
+public interface IQueryListener<S, R> {
+
+ void queryComplete(Query<S, R> job, R result);
+
+ void queryFailed(Query<S, R> job, Exception error);
+
+ void queryCanceled(Query<S, R> job);
+
+}
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2007 VTT Technical Research Centre of Finland and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.query;
+
+
+/**
+ * @author Tuukka Lehtonen
+ *
+ * @param <S>
+ * @param <R>
+ */
+public class Query<S, R> {
+
+ public final S source;
+
+ public final IQueryListener<S, R> listener;
+
+ public Query(S source, IQueryListener<S, R> listener) {
+ assert source != null;
+ assert listener != null;
+
+ this.source = source;
+ this.listener = listener;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getName() + " [" + source + "]";
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.sg;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.Image;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ImageObserver;
+import java.awt.image.VolatileImage;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.simantics.maps.WebService;
+import org.simantics.maps.osm.OSMTileProvider;
+import org.simantics.maps.pojo.TileJobQueue;
+import org.simantics.maps.tile.IFilter;
+import org.simantics.maps.tile.ITileJobQueue;
+import org.simantics.maps.tile.ITileListener;
+import org.simantics.maps.tile.ITileProvider;
+import org.simantics.maps.tile.TileCache;
+import org.simantics.maps.tile.TileKey;
+import org.simantics.scenegraph.g2d.G2DNode;
+import org.simantics.scenegraph.g2d.G2DRenderingHints;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class MapNode extends G2DNode implements ITileListener {
+
+ private static final long serialVersionUID = 2490944880914577411L;
+
+ private final double MAP_SCALE = 1;
+ private final int MAX_TILE_LEVEL = 19;
+ private final int TILE_PIXEL_SIZE = 256;
+ private final int VIEWBOX_QUIET_TIME = 500;
+
+ protected Boolean enabled = true;
+
+ enum TileState {
+ NOT_LOADED,
+ LOADING,
+ LOADED,
+ NOT_AVAILABLE
+ }
+
+ class TileLevel extends HashSet<TileKey> {
+ private static final long serialVersionUID = 5743763238677223952L;
+ }
+
+ static class TileTraverser {
+ List<TileKey> tile = new ArrayList<TileKey>(4);
+ List<Integer> quadrant = new ArrayList<Integer>(4);
+
+ static int getTileQuadrant(TileKey k) {
+ int x = k.getX();
+ int y = k.getY();
+ if ((x & 1) == 0) {
+ return ((y & 1) == 0) ? 1 : 2;
+ }
+ return ((y & 1) == 0) ? 0 : 3;
+ }
+
+ void clear() {
+ tile.clear();
+ quadrant.clear();
+ }
+
+ void add(TileKey tile) {
+ this.tile.add(tile);
+ this.quadrant.add(getTileQuadrant(tile));
+ }
+
+ int size() {
+ return tile.size();
+ }
+
+ TileKey getTile(int i) {
+ return tile.get(i);
+ }
+
+ int getQuadrant(int i) {
+ return quadrant.get(i);
+ }
+ }
+
+ public class ObjectHolder<T>
+ {
+ private T o = null;
+ public T get() {
+ return o;
+ }
+ public void set(T o) {
+ this.o = o;
+ }
+ }
+
+ public final class Pair<T1, T2> {
+ public final T1 first;
+ public final T2 second;
+ public Pair(T1 first, T2 second) {
+ this.first = first;
+ this.second = second;
+ }
+ }
+
+ private TileCache tileCache;
+
+ // For delaying the retrieval of map data until the viewport remains steady
+ private ScheduledFuture<?> pendingTileTask;
+
+ private Map<Integer, TileLevel> neededTiles = new HashMap<Integer, TileLevel>();
+
+ private TileTraverser tileTraverser = new TileTraverser();
+
+ private ITileJobQueue job = null;
+
+ private Map<TileKey, TileState> tileStates = new Hashtable<TileKey, TileState>();
+ private ObjectHolder<VolatileImage> notLoadedImage = new ObjectHolder<VolatileImage>();
+ private ObjectHolder<VolatileImage> loadingImage = new ObjectHolder<VolatileImage>();
+ private ObjectHolder<VolatileImage> notAvailableImage = new ObjectHolder<VolatileImage>();
+
+ private Font textFont = new Font(Font.SANS_SERIF, Font.BOLD, 40);
+
+ private AffineTransform oldTransform = new AffineTransform();
+ private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
+
+ private Composite normalComposite = AlphaComposite.SrcOver.derive(1f);
+ private Composite parentDataComposite = AlphaComposite.SrcOver.derive(0.75f);
+
+ private Rectangle2D helperRect = new Rectangle2D.Double();
+
+ ImageObserver observer = new ImageObserver() {
+ @Override
+ public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
+// System.out.format("imageUpdate(%s, %d, %d, %d, %d, %d)\n", img.toString(), infoflags, x, y, width, height);
+ return true;
+ }
+ };
+
+ private int scale;
+
+ public void init() {
+ try {
+ ITileProvider provider = new OSMTileProvider(new WebService("http://localhost:8080/osm-bright/"), TILE_PIXEL_SIZE);
+
+ // Try to load eclipse specific implementation of TileJobQueue, if it doesn't exist, fall back to pojo implementation
+ try {
+ Class<?> proxyClass = (Class<?>) Class.forName("org.simantics.maps.eclipse.TileJobQueue");
+ job = (ITileJobQueue)proxyClass.newInstance();
+ } catch (ClassNotFoundException e1) {
+ } catch (InstantiationException e) {
+ } catch (IllegalAccessException e) {
+ }
+ // AppletProxyUtil should exist always..
+ if(job == null) {
+ job = new TileJobQueue();
+ }
+
+ // Cache is also eclipse specific...
+ ITileProvider cachedProvider = null;
+ try {
+ Class<?> proxyClass = (Class<?>) Class.forName("org.simantics.maps.eclipse.DiskCachingTileProvider");
+ cachedProvider = (ITileProvider)proxyClass.getConstructor(ITileProvider.class, Boolean.class).newInstance(provider, false);
+ } catch (ClassNotFoundException e1) {
+ } catch (InstantiationException e) {
+ } catch (IllegalAccessException e) {
+ }
+
+ if(cachedProvider != null) {
+ job.setTileProvider(cachedProvider);
+ } else {
+ job.setTileProvider(provider);
+ }
+
+ this.tileCache = new TileCache(job);
+ this.tileCache.addTileListener(this);
+ } catch (MalformedURLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (URISyntaxException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch(Exception e ) {
+ e.printStackTrace();
+ }
+ }
+
+ @SyncField("enabled")
+ public void setEnabled(Boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @Override
+ public void render(Graphics2D g2d) {
+ if (!enabled)
+ return;
+
+ Graphics2D g = (Graphics2D)g2d.create();
+
+ AffineTransform tr = (AffineTransform)g.getTransform().clone();
+
+ double scaleX = Math.abs(tr.getScaleX());
+ double scaleY = Math.abs(tr.getScaleY());
+ if (scaleX <= 0 || scaleY <= 0) {
+ // Make sure that we don't end up in an eternal loop below.
+ return;
+ }
+ double offsetX = -tr.getTranslateX();
+ double offsetY = -tr.getTranslateY();
+
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ Rectangle2D sp = localToControl(new Rectangle2D.Double(0.0, 0.0, 1.0, 1.0));
+ Rectangle2D b = (Rectangle2D)((Rectangle)g.getRenderingHint(G2DRenderingHints.KEY_CONTROL_BOUNDS)).getBounds2D(); // getClipBounds is not accurate enough, use original clipbounds and scale here
+
+ Rectangle2D viewbox = new Rectangle2D.Double(offsetX/scaleX, offsetY/scaleY, b.getWidth()/sp.getWidth(), b.getHeight()/sp.getHeight()); //g.getClipBounds();
+ if(viewbox == null) return; // FIXME
+
+ double smallerViewboxDimension = viewbox.getWidth() < viewbox.getHeight() ? viewbox.getWidth() * MAP_SCALE : viewbox.getHeight() * MAP_SCALE;
+ int level = 0;
+ double tileSize = get360Scaled() * MAP_SCALE*2;
+ while (level < MAX_TILE_LEVEL) {
+ double ratio = smallerViewboxDimension / tileSize;
+ if (ratio >= 0.85) {
+ break;
+ }
+ tileSize *= 0.5;
+ level++;
+ }
+
+ /*
+ * To convert y-coordinates to map coordinates in ruler, use:
+ * double val = (y-offsetY)/scaleY;
+ * val = Math.toDegrees(Math.atan(Math.sinh(Math.toRadians(val))));
+ * String str = formatValue(val);
+ */
+
+ double minx = Math.min(get180Scaled(), Math.max(viewbox.getMinX(), -get180Scaled()));
+ double maxx = Math.min(get180Scaled(), Math.max(viewbox.getMaxX(), -get180Scaled()));
+
+ double miny = Math.min(get360Scaled(), Math.max(viewbox.getMinY()+get180Scaled(), 0));
+ double maxy = Math.min(get360Scaled(), Math.max(viewbox.getMaxY()+get180Scaled(), 0));
+
+ g.setTransform(new AffineTransform());
+ g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+
+ int levels = (1 << level);
+
+ // http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
+ int left = (int)Math.floor( (minx + get180Scaled()) / get360Scaled() * (1<<level) );
+ int right = (int)Math.floor( (maxx + get180Scaled()) / get360Scaled() * (1<<level) );
+ int top = (int)Math.floor(miny / get360Scaled() * (1<<level));// (int)Math.floor( (1 - Math.log(Math.tan(Math.toRadians(miny)) + 1 / Math.cos(Math.toRadians(miny))) / Math.PI) / 2 * (1<<level) );
+ int bottom = (int)Math.floor(maxy / get360Scaled() * (1<<level));// (int)Math.floor( (1 - Math.log(Math.tan(Math.toRadians(maxy)) + 1 / Math.cos(Math.toRadians(maxy))) / Math.PI) / 2 * (1<<level) );
+
+ double tsx = get360Scaled() / (double)levels; // Size of tile on zoom level
+ for(int tx = left; tx <= right; tx++) {
+ if(tx < 0 || tx >= levels) continue;
+ for(int ty = top; ty <= bottom; ty++) {
+ if(ty < 0 || ty >= levels) continue;
+ TileKey tile = new TileKey(level, tx, ty);
+ double y = (double)ty - (double)levels/2; // In level 0 we have only one tile
+ paintTile(tileCache, g, tr, tile, tx*tsx-get180Scaled(), y*tsx, tsx);
+ }
+ }
+ g.dispose();
+
+ // Check if transform has changed
+ transformChanged(tr, level);
+ }
+
+ private double get360Scaled() {
+ return 360 * scale;
+ }
+
+ private double get180Scaled() {
+ return 180 * scale;
+ }
+
+ @Override
+ public Rectangle2D getBoundsInLocal() {
+ return null;
+ }
+
+ private void paintTile(TileCache cache, Graphics2D g, AffineTransform userToScreenTransform, TileKey tile, double tx, double ty, double tileSize) {
+ GraphicsConfiguration gc = g.getDeviceConfiguration();
+
+ Image img = null;
+ boolean usingParentData = false;
+ helperRect.setFrame(0, 0, TILE_PIXEL_SIZE, TILE_PIXEL_SIZE);
+
+ TileState state = tileStates.get(tile);
+ if (state == null)
+ state = TileState.NOT_LOADED;
+
+ switch (state) {
+ case NOT_LOADED:
+ case LOADED:
+ {
+ // The same logic applies both when the data has not been loaded
+ // or the data has been loaded previously, but may have been
+ // flushed from the cache.
+ img = cache.peek(tile);
+ if (img == null) {
+ // Either the data has never existed before or the cache has
+ // flushed this tile. Mark it as needed and try to use
+ // parent image instead.
+ needTile(tile);
+
+ Pair<TileKey, Image> p = findFirstAvailableParentTile(cache, tile, tileTraverser);
+ if (p != null) {
+ img = p.second;
+ helperRect.setFrame(0, 0, img.getWidth(observer), img.getHeight(observer));
+ traverseRectangle(tileTraverser, helperRect);
+ usingParentData = true;
+ } else {
+ img = getNotLoadedImage(gc);
+ }
+ } else {
+ helperRect.setFrame(0, 0, img.getWidth(observer), img.getHeight(observer));
+ }
+ break;
+ }
+ case LOADING: {
+ Pair<TileKey, Image> p = findFirstAvailableParentTile(cache, tile, tileTraverser);
+ if (p != null) {
+ img = p.second;
+ helperRect.setFrame(0, 0, img.getWidth(observer), img.getHeight(observer));
+ traverseRectangle(tileTraverser, helperRect);
+ usingParentData = true;
+ } else {
+ img = getLoadingImage(gc);
+ }
+ break;
+ }
+ case NOT_AVAILABLE: {
+ img = getMissingImage(gc);
+ break;
+ }
+ default:
+ throw new Error("Invalid tile state: " + state);
+ }
+
+ // Calculate where these user space coordinates are in the screen
+ // space and given them to drawImage().
+ Point2D helperPoint1 = new Point2D.Double(tx, ty);
+ Point2D helperPoint2 = new Point2D.Double(tx + tileSize, ty + tileSize);
+ userToScreenTransform.transform(helperPoint1, helperPoint1);
+ userToScreenTransform.transform(helperPoint2, helperPoint2);
+
+ int dx1 = (int) Math.round(helperPoint1.getX());
+ int dy1 = (int) Math.round(helperPoint1.getY());
+ int dx2 = (int) Math.round(helperPoint2.getX());
+ int dy2 = (int) Math.round(helperPoint2.getY());
+
+ int sx1 = (int) Math.round(helperRect.getMinX());
+ int sy1 = (int) Math.round(helperRect.getMinY());
+ int sx2 = (int) Math.round(helperRect.getMaxX());
+ int sy2 = (int) Math.round(helperRect.getMaxY());
+
+ if (usingParentData) {
+ g.setComposite(parentDataComposite);
+ } else {
+ g.setComposite(normalComposite);
+ }
+ g.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
+ }
+
+ private void transformChanged(AffineTransform newTransform, final int level) {
+ // Cancel data fetching if the viewport changes.
+ AffineTransform ato = (AffineTransform) oldTransform;
+ AffineTransform atn = (AffineTransform) newTransform;
+
+ double s1 = -1;
+ if (ato != null)
+ s1 = ato.getScaleX();
+ double s2 = atn.getScaleX();
+ double ds = s2 - s1;
+ if (Math.abs(ds) > 1e-6) {
+ if (pendingTileTask != null) {
+ pendingTileTask.cancel(false);
+ pendingTileTask = null;
+ }
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ // There was a quiet period of [delay] milliseconds.
+ // Now its time to start fetching the map data that has
+ // piled up into the queue.
+ loadNeededTiles(level);
+ }
+ };
+ pendingTileTask = scheduler.schedule(r, VIEWBOX_QUIET_TIME, TimeUnit.MILLISECONDS);
+ oldTransform = (AffineTransform)newTransform.clone();
+ } else {
+ loadNeededTiles(level);
+ }
+ }
+
+ private void loadNeededTiles(final int level) {
+ TileCache cache = this.tileCache;
+
+ // Remove all tile queries from the job queue that do not match the
+ // requested tile level. This helps speed up loading of the right tiles
+ // that should be shown to the user ASAP instead of spending time
+ // loading invisible tiles.
+ cache.filterJobQueue(new IFilter<TileKey>() {
+ @Override
+ public boolean select(TileKey tile) {
+ boolean result = (tile.getLevel() == level);
+ //if (!result)
+ // System.out.println("FILTERED TILE: " + tile);
+ return result;
+ }
+ });
+
+ // Schedule retrieval of each tile on the current level.
+ TileLevel l = null;
+ synchronized (neededTiles) {
+ l = neededTiles.get(level);
+ neededTiles.clear();
+ }
+ if (l != null && !l.isEmpty()) {
+ for (TileKey k : l) {
+ tileStates.put(k, TileState.LOADING);
+ tileCache.get(k);
+ }
+ }
+ }
+
+ void needTile(TileKey t) {
+ synchronized (neededTiles) {
+ int level = t.getLevel();
+ TileLevel l = neededTiles.get(level);
+ if (l == null) {
+ l = new TileLevel();
+ neededTiles.put(level, l);
+ }
+ l.add(t);
+ }
+ }
+
+ private Pair<TileKey, Image> findFirstAvailableParentTile(TileCache cache, TileKey tile, TileTraverser traverser) {
+ traverser.clear();
+ traverser.add(tile);
+
+ TileKey parent = getParentTile(tile);
+ while (parent != null) {
+ Image img = cache.peek(parent);
+ if (img != null)
+ return new Pair<TileKey, Image>(parent, img);
+ traverser.add(parent);
+ parent = getParentTile(parent);
+ }
+ return null;
+ }
+
+ private TileKey getParentTile(TileKey k) {
+ if (k.getLevel() == 0)
+ return null;
+ return new TileKey(k.getLevel() - 1, k.getX() / 2, k.getY() / 2);
+ }
+
+ private void traverseRectangle(TileTraverser traverser, Rectangle2D rect) {
+ for (int i = traverser.size() - 1; i >= 0; --i) {
+ traverseToQuadrant(rect, traverser.getQuadrant(i));
+ }
+ }
+
+ private Rectangle2D traverseToQuadrant(Rectangle2D r, int quadrant) {
+ boolean left = quadrant == 1 || quadrant == 2;
+ boolean top = quadrant == 1 || quadrant == 0;
+ r.setFrame(
+ left ? r.getMinX() : r.getCenterX(),
+ top ? r.getMinY() : r.getCenterY(),
+ r.getWidth() / 2.0,
+ r.getHeight() / 2.0);
+ return r;
+ }
+
+ private synchronized VolatileImage getNotLoadedImage(GraphicsConfiguration gc) {
+ return validateImageWithCenteredText(gc, notLoadedImage, TILE_PIXEL_SIZE, "NOT LOADED", Color.LIGHT_GRAY, Color.BLACK);
+ }
+
+ private synchronized VolatileImage getLoadingImage(GraphicsConfiguration gc) {
+ return validateImageWithCenteredText(gc, loadingImage, TILE_PIXEL_SIZE, "LOADING", Color.BLUE, Color.BLACK);
+ }
+
+ private synchronized VolatileImage getMissingImage(GraphicsConfiguration gc) {
+ return validateImageWithCenteredText(gc, notAvailableImage, TILE_PIXEL_SIZE, "NOT AVAILABLE", Color.RED, Color.DARK_GRAY);
+ }
+
+ private void drawImageWithCenteredText(VolatileImage image, String str, Font font, Color color, Color bgColor) {
+ Graphics2D target = image.createGraphics();
+ Rectangle2D bb = target.getFontMetrics(font).getStringBounds(str, target);
+
+ target.setBackground(new Color(255,255,255,0));
+ int w = image.getWidth();
+ int h = image.getHeight();
+
+ target.setColor(Color.BLACK);
+ target.fillRect(0, 0, w, h);
+ target.setColor(bgColor);
+ target.fillRect(2, 2, w - 3, h - 3);
+ target.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ target.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ target.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
+ target.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ target.setColor(color);
+ target.setFont(font);
+ target.drawString(str, (float) (((double)w - bb.getWidth()) / 2), (float) (((double)h + bb.getHeight()) / 2));
+ target.dispose();
+ }
+
+ private VolatileImage validateImageWithCenteredText(GraphicsConfiguration gc, ObjectHolder<VolatileImage> container, int size, String text, Color textColor, Color bgColor) {
+ VolatileImage image = container.get();
+ if (image != null) {
+ int validateResult = image.validate(gc);
+ if (validateResult == VolatileImage.IMAGE_INCOMPATIBLE) {
+ image = null;
+ } else if (validateResult == VolatileImage.IMAGE_OK) {
+ return image;
+ } else if (validateResult == VolatileImage.IMAGE_RESTORED) {
+ drawImageWithCenteredText(image, text, textFont, textColor, bgColor);
+ }
+ }
+ if (image == null) {
+ image = gc.createCompatibleVolatileImage(size, size);
+ container.set(image);
+ drawImageWithCenteredText(image, text, textFont, textColor, bgColor);
+ }
+ return image;
+ }
+
+ @Override
+ public void tileCanceled(TileKey key) {
+ tileStates.put(key, TileState.NOT_LOADED);
+ }
+
+ @Override
+ public void tileFailed(TileKey key, Throwable e) {
+ tileStates.put(key, TileState.NOT_AVAILABLE);
+ repaint();
+ }
+
+ @Override
+ public void tileUpdated(TileKey key, Image image) {
+ tileStates.put(key, TileState.LOADED);
+ repaint();
+ }
+
+ public void setScale(int scale) {
+ this.scale = scale;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tests;
+/*
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+
+import org.eclipse.swt.widgets.Composite;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.chassis.ICanvasChassis;
+import org.simantics.g2d.canvas.chassis.IChassisListener;
+import org.simantics.g2d.canvas.chassis.ViewPartChassis;
+import org.simantics.utils.threads.AWTThread;
+import org.simantics.utils.threads.IThreadWorkQueue;
+
+public class View extends ViewPartChassis {
+ public static final String ID = "canvasEditorTest.view";
+
+ int handle;
+
+ @Override
+ public void createPartControl(Composite parent) {
+ // TODO Auto-generated method stub
+ super.createPartControl(parent);
+
+ IThreadWorkQueue thread = AWTThread.getThreadAccess();
+ GraphicsConfiguration gc = getAwtGraphicsConfiguration();
+// CanvasContext canvasContext = TestCanvas.createTestCanvas(thread, gc);
+// setCanvasContext(canvasContext);
+
+ this.addChassisListener(new IChassisListener() {
+ @Override
+ public void chassisClosed(ICanvasChassis sender) {
+ ICanvasContext ctx = getCanvasContext();
+ if (ctx != null)
+ ctx.dispose();
+ }
+ });
+
+// IDiagramContext diagramCtx = new DiagramContextImpl();
+// DiagramImpl diagram = new DiagramImpl();
+// diagramCtx.setDiagram(diagram);
+// new DiagramPainter(canvasContext, diagramCtx);
+// Box box = new Box();
+// box.setSize(100, 100);
+// diagram.addPart(box);
+// diagram.translate(box, 50, 50);
+ }
+
+ @Override
+ public void paint(Graphics2D g) {
+ if (canvasContext == null)
+ return;
+
+
+
+ super.paint(g);
+ }
+
+}
+*/
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tests;
+
+import java.awt.geom.Rectangle2D;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+
+import org.simantics.maps.WebService;
+import org.simantics.maps.wms.WMSGetMapQuery;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class WMSTest1 {
+
+ private static String serviceUrl = "http://wms.jpl.nasa.gov/wms.cgi";
+
+ public static void main(String[] args) throws MalformedURLException, URISyntaxException {
+ // Create a method instance.
+ WebService service = new WebService(serviceUrl);
+ WMSGetMapQuery req = new WMSGetMapQuery(600, 300, new Rectangle2D.Double(-180,-90,360,180), "image/jpeg", "global_mosaic");
+
+ HttpURLConnection connection = null;
+ try {
+ connection = service.openConnection("", req.toString());
+ // Execute the method.
+ int statusCode = connection.getResponseCode();
+
+ if (statusCode != HttpURLConnection.HTTP_OK) {
+ System.err.println("Method failed: " + connection.getResponseMessage());
+ }
+
+ // Read the response body.
+ byte[] buf = new byte[16384];
+ int read = 0;
+
+ InputStream response = connection.getInputStream();
+ FileOutputStream fo = new FileOutputStream(File.createTempFile("wmsresponse", ""));
+ try {
+ while ((read = response.read(buf)) != -1) {
+ fo.write(buf, 0, read);
+ }
+ } finally {
+ fo.close();
+ response.close();
+ }
+
+ } catch (IOException e) {
+ System.err.println("Fatal transport error: " + e.getMessage());
+ e.printStackTrace();
+ } finally {
+ // Release the connection.
+ connection.disconnect();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tests;
+/*
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+
+import javax.imageio.ImageIO;
+import javax.swing.SwingUtilities;
+
+import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.params.HttpMethodParams;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.chassis.FullscreenAwtChassis;
+import org.simantics.g2d.canvas.chassis.ICanvasChassis;
+import org.simantics.g2d.canvas.chassis.IChassisListener;
+import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
+import org.simantics.g2d.canvas.impl.CanvasContext;
+import org.simantics.g2d.canvas.impl.PainterReflection.Painter;
+import org.simantics.g2d.canvas.participant.GridPainter;
+import org.simantics.g2d.canvas.participant.MouseMonitor;
+import org.simantics.g2d.canvas.participant.PanZoomInteractor;
+import org.simantics.utils.threads.AWTThread;
+
+import org.simantics.maps.Service;
+import org.simantics.maps.WebService;
+import org.simantics.maps.wms.WMSGetMapQuery;
+
+public class WMSTest2 {
+
+ private static String serviceUrl = "http://wms.jpl.nasa.gov/wms.cgi";
+
+
+ WMSTest2() throws URISyntaxException {
+ try {
+ final Service service = new WebService(serviceUrl);
+ final BufferedImage bi = getData(service, new Dimension(600, 300), new Rectangle2D.Double(-180,-90,360,180), 1);
+
+ GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
+ FullscreenAwtChassis chassis = new FullscreenAwtChassis("WMS Test", true);
+ ICanvasContext canvasContext = new CanvasContext(AWTThread.getThreadAccess());
+ chassis.setCanvasContext(canvasContext);
+
+ // Paints a cursor, follows mouse movement
+ new MouseMonitor(canvasContext);
+ // Pan & Zoom features
+ new PanZoomInteractor(canvasContext);
+ GridPainter grid = new GridPainter(canvasContext);
+ // Ruler painter
+// new RulerPainter(canvasContext);
+
+ new AbstractCanvasParticipant(canvasContext) {
+ @Painter(priority = Integer.MAX_VALUE - 1000)
+ public void paint(Graphics2D g) {
+ g.drawImage(bi, new AffineTransformOp(new AffineTransform(), AffineTransformOp.TYPE_BICUBIC), 100, 100);
+ }
+ };
+
+ chassis.addChassisListener(new IChassisListener() {
+ @Override
+ public void chassisClosed(ICanvasChassis sender) {
+ ICanvasContext ctx = sender.getCanvasContext();
+ if (ctx!=null)
+ ctx.dispose();
+ }});
+
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ }
+*/
+ /**
+ * Get a bufferedimage of the map data for the geographical coordinates.
+ *
+ * @param x
+ * @param y
+ * @param z
+ * @return
+ * @throws MalformedURLException
+ */
+/*
+ public BufferedImage getData(Service service, Dimension rasterSize, Rectangle2D bbox, double zoom) throws MalformedURLException {
+ if (rasterSize.width == 0 || rasterSize.height == 0)
+ return new BufferedImage(0, 0, BufferedImage.TYPE_INT_ARGB);
+
+ // Create an instance of HttpClient.
+ HttpClient client = new HttpClient();
+
+ // Create a method instance.
+ WMSGetMapQuery req = new WMSGetMapQuery(rasterSize.width, rasterSize.height, bbox, "image/jpeg", "global_mosaic");
+ GetMethod method = new GetMethod(service.getRequestURL(req.toString()));
+
+ // Provide custom retry handler is necessary
+ method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
+ new DefaultHttpMethodRetryHandler(3, false));
+
+ try {
+ // Execute the method.
+ int statusCode = client.executeMethod(method);
+
+ if (statusCode != HttpStatus.SC_OK) {
+ System.err.println("Method failed: " + method.getStatusLine());
+ }
+
+ InputStream response = method.getResponseBodyAsStream();
+ BufferedImage img = ImageIO.read(response);
+ if (img == null) {
+ // Maybe the method produced a WMS error XML?
+ return null;
+ }
+ return img;
+ } catch (HttpException e) {
+ System.err.println("Fatal protocol violation: " + e.getMessage());
+ e.printStackTrace();
+ } catch (IOException e) {
+ System.err.println("Fatal transport error: " + e.getMessage());
+ e.printStackTrace();
+ } finally {
+ // Release the connection.
+ method.releaseConnection();
+ }
+ return null;
+ }
+
+
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ new WMSTest2();
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+}
+*/
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tests;
+/*
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+
+import javax.imageio.ImageIO;
+import javax.swing.SwingUtilities;
+
+import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.params.HttpMethodParams;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.chassis.FullscreenAwtChassis;
+import org.simantics.g2d.canvas.chassis.ICanvasChassis;
+import org.simantics.g2d.canvas.chassis.IChassisListener;
+import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
+import org.simantics.g2d.canvas.impl.CanvasContext;
+import org.simantics.g2d.canvas.impl.PainterReflection.Painter;
+import org.simantics.g2d.canvas.participant.GridPainter;
+import org.simantics.g2d.canvas.participant.MouseMonitor;
+import org.simantics.g2d.canvas.participant.PanZoomInteractor;
+import org.simantics.utils.threads.AWTThread;
+
+import org.simantics.maps.Service;
+import org.simantics.maps.WebService;
+import org.simantics.maps.wms.WMSGetMapQuery;
+
+public class WMSTest3 {
+
+ private static String serviceUrl = "http://wms.jpl.nasa.gov/wms.cgi";
+
+
+ WMSTest3() throws URISyntaxException {
+ try {
+ final Service service = new WebService(serviceUrl);
+ final BufferedImage bi = getData(service, new Dimension(600, 300), new Rectangle2D.Double(-180,-90,360,180), 1);
+
+ GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
+ FullscreenAwtChassis chassis = new FullscreenAwtChassis("WMS Test", true);
+ ICanvasContext canvasContext = new CanvasContext(AWTThread.getThreadAccess()); chassis.setCanvasContext(canvasContext);
+
+ // Paints a cursor, follows mouse movement
+ new MouseMonitor(canvasContext);
+ // Pan & Zoom features
+ new PanZoomInteractor(canvasContext);
+ GridPainter grid = new GridPainter(canvasContext);
+ // Ruler painter
+// new RulerPainter(canvasContext);
+
+ new AbstractCanvasParticipant(canvasContext) {
+ @Painter(priority = Integer.MAX_VALUE - 1000)
+ public void paint(Graphics2D g) {
+ g.drawImage(bi, new AffineTransformOp(new AffineTransform(), AffineTransformOp.TYPE_BICUBIC), 100, 100);
+ }
+ };
+
+ chassis.addChassisListener(new IChassisListener() {
+ @Override
+ public void chassisClosed(ICanvasChassis sender) {
+ ICanvasContext ctx = sender.getCanvasContext();
+ if (ctx!=null)
+ ctx.dispose();
+ }});
+
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ }
+*/
+ /**
+ * Get a bufferedimage of the map data for the geographical coordinates.
+ *
+ * @param x
+ * @param y
+ * @param z
+ * @return
+ * @throws MalformedURLException
+ */
+/*
+ public BufferedImage getData(Service service, Dimension rasterSize, Rectangle2D bbox, double zoom) throws MalformedURLException {
+ if (rasterSize.width == 0 || rasterSize.height == 0)
+ return new BufferedImage(0, 0, BufferedImage.TYPE_INT_ARGB);
+
+ // Create an instance of HttpClient.
+ HttpClient client = new HttpClient();
+
+ // Create a method instance.
+ WMSGetMapQuery req = new WMSGetMapQuery(rasterSize.width, rasterSize.height, bbox, "image/jpeg", "global_mosaic");
+ GetMethod method = new GetMethod(service.getRequestURL(req.toString()));
+
+ // Provide custom retry handler is necessary
+ method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
+ new DefaultHttpMethodRetryHandler(3, false));
+
+ try {
+ // Execute the method.
+ int statusCode = client.executeMethod(method);
+
+ if (statusCode != HttpStatus.SC_OK) {
+ System.err.println("Method failed: " + method.getStatusLine());
+ }
+
+ InputStream response = method.getResponseBodyAsStream();
+ BufferedImage img = ImageIO.read(response);
+ if (img == null) {
+ // Maybe the method produced a WMS error XML?
+ return null;
+ }
+ return img;
+ } catch (HttpException e) {
+ System.err.println("Fatal protocol violation: " + e.getMessage());
+ e.printStackTrace();
+ } catch (IOException e) {
+ System.err.println("Fatal transport error: " + e.getMessage());
+ e.printStackTrace();
+ } finally {
+ // Release the connection.
+ method.releaseConnection();
+ }
+ return null;
+ }
+
+
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ new WMSTest3();
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+}
+*/
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tile;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.simantics.maps.ProvisionException;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class CompoundTileProvider implements ITileProvider {
+
+ URI source;
+
+ int tileSize;
+
+ static class Provider {
+ final ITileProvider provider;
+ final int minTileLevel;
+ final int maxTileLevel;
+ public Provider(ITileProvider provider, int minTileLevel, int maxTileLevel) {
+ this.provider = provider;
+ this.minTileLevel = minTileLevel;
+ this.maxTileLevel = maxTileLevel;
+ }
+ }
+
+ List<Provider> providers = new ArrayList<Provider>();
+
+ public CompoundTileProvider(URI source, int tileSize) {
+ this.source = source;
+ this.tileSize = tileSize;
+ }
+
+ @Override
+ public URI getSource() {
+ return source;
+ }
+
+ @Override
+ public Rectangle2D getExtent() {
+ if (providers.isEmpty())
+ return new Rectangle2D.Double();
+ boolean first = true;
+ Rectangle2D result = new Rectangle2D.Double();
+ for (Provider p : providers) {
+ if (first) {
+ result.setFrame(p.provider.getExtent());
+ first = false;
+ } else {
+ Rectangle2D.union(result, p.provider.getExtent(), result);
+ }
+ }
+ return result;
+ }
+
+ public void addProvider(ITileProvider provider, int minTileLevel, int maxTileLevel) {
+ if (minTileLevel > maxTileLevel)
+ throw new IllegalArgumentException("minTileLevel (" + minTileLevel + ") cannot be larger than maxTileLevel" + maxTileLevel + ")");
+ providers.add(new Provider(provider, minTileLevel, maxTileLevel));
+ }
+
+ @Override
+ public Image get(TileKey key) throws ProvisionException {
+ // Prefer the tile of a provider that has larger minZoom value when the requested tile is completely covered by the provider.
+ // If the tile is only partially covered by the service with the larger minZoom, combine it with another provider's tile.
+
+ int level = key.getLevel();
+ int x = key.getX();
+ int y = key.getY();
+
+ double xTiles = Math.pow(2, level + 1);
+ double yTiles = Math.pow(2, level);
+
+ if (level < 0)
+ throw new IllegalArgumentException("invalid tile level " + level + " (tile=" + key +")");
+ if (x < 0 || x >= (int) xTiles)
+ throw new IllegalArgumentException("tile x out of bounds " + x + " (tile=" + key +")");
+ if (y < 0 || y >= (int) yTiles)
+ throw new IllegalArgumentException("tile y out of bounds " + y + " (tile=" + key +")");
+
+ Rectangle2D r = new Rectangle2D.Double();
+
+ double minx = -180;
+ double miny = -90;
+ double w = 360;
+ double h = 180;
+
+ double xdelta = w / xTiles;
+ double ydelta = h / yTiles;
+ double xx = x;
+ double yy = yTiles - y - 1;
+
+ r.setFrame(
+ minx + xdelta*xx,
+ miny + ydelta*yy,
+ xdelta,
+ ydelta
+ );
+
+ BufferedImage result = null;
+ Graphics2D g = null;
+ try {
+ for (Provider p : providers) {
+ if (level < p.minTileLevel) {
+// System.out.println("Provider " + p.provider + " tile level too small: " + level + " < " + p.minTileLevel);
+ continue;
+ }
+ Rectangle2D extent = p.provider.getExtent();
+ if (!r.intersects(extent)) {
+// System.out.println("Provider " + p.provider + ": " + r + " out of bounds: " + extent);
+ continue;
+ }
+
+ if (result == null) {
+ result = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_3BYTE_BGR);
+ g = result.createGraphics();
+ }
+
+ if (level > p.maxTileLevel) {
+// System.out.println("Provider " + p.provider + " tile level too large: " + level + " > " + p.maxTileLevel);
+ Rectangle2D helperRect = new Rectangle2D.Double(0, 0, tileSize, tileSize);
+ TileTraverser tileTraverser = new TileTraverser();
+
+ TileKey maxTile = findFirstAvailableParentTile(p.maxTileLevel, key, tileTraverser);
+ Image img = p.provider.get(maxTile);
+ helperRect.setFrame(0, 0, img.getWidth(null), img.getHeight(null));
+ traverseRectangle(tileTraverser, helperRect);
+
+ int sx1 = (int) Math.round(helperRect.getMinX());
+ int sy1 = (int) Math.round(helperRect.getMinY());
+ int sx2 = (int) Math.round(helperRect.getMaxX());
+ int sy2 = (int) Math.round(helperRect.getMaxY());
+
+ g.drawImage(img, 0, 0, tileSize, tileSize, sx1, sy1, sx2, sy2, null);
+ } else {
+ Image src = p.provider.get(key);
+ int srcW = src.getWidth(null);
+ int srcH = src.getHeight(null);
+ g.drawImage(src, 0, 0, tileSize, tileSize, 0, 0, srcW, srcH, null);
+ }
+ }
+ } finally {
+ if (g != null)
+ g.dispose();
+ }
+
+ return result != null ? result : getOutOfBoundsImage();
+ }
+
+ BufferedImage outOfBoundsImage;
+
+ private Image getOutOfBoundsImage() {
+ if (outOfBoundsImage != null)
+ return outOfBoundsImage;
+
+ final int outOfBoundsImageSize = 1;
+ BufferedImage image = new BufferedImage(outOfBoundsImageSize, outOfBoundsImageSize, BufferedImage.TYPE_3BYTE_BGR);
+ Graphics2D g = image.createGraphics();
+ try {
+ g.setColor(Color.CYAN);
+ g.fillRect(0, 0, outOfBoundsImageSize, outOfBoundsImageSize);
+ outOfBoundsImage = image;
+ return image;
+ } finally {
+ g.dispose();
+ }
+ }
+
+ /**
+ * Quadrants:
+ *
+ * 1|0
+ * ---
+ * 2|3
+ *
+ * @param k
+ * @return
+ */
+ TileKey getChildTile(TileKey k, int quadrant) {
+ int ox = quadrant == 0 || quadrant == 3 ? 1 : 0;
+ int oy = quadrant == 0 || quadrant == 1 ? 1 : 0;
+ return new TileKey(k.getLevel() + 1, k.getX() * 2 + ox, k.getY() * 2 + oy);
+ }
+
+ TileKey getParentTile(TileKey k) {
+ if (k.getLevel() == 0)
+ return null;
+ return new TileKey(k.getLevel() - 1, k.getX() / 2, k.getY() / 2);
+ }
+
+ Rectangle2D traverseToQuadrant(Rectangle2D r, int quadrant) {
+ boolean left = quadrant == 1 || quadrant == 2;
+ boolean top = quadrant == 1 || quadrant == 0;
+ r.setFrame(
+ left ? r.getMinX() : r.getCenterX(),
+ top ? r.getMinY() : r.getCenterY(),
+ r.getWidth() / 2.0,
+ r.getHeight() / 2.0);
+ return r;
+ }
+
+ int findTilePos(int level, double t, double tileSize) {
+ // Change 0 to change the world origin.
+ double dt = t - 0;
+ double p = dt / tileSize;
+ int tt = (int) Math.floor(p);
+ return tt;
+ }
+
+ static class TileTraverser {
+ List<TileKey> tile = new ArrayList<TileKey>(4);
+ List<Integer> quadrant = new ArrayList<Integer>(4);
+
+ static int getTileQuadrant(TileKey k) {
+ int x = k.getX();
+ int y = k.getY();
+ if ((x & 1) == 0) {
+ return ((y & 1) == 0) ? 1 : 2;
+ }
+ return ((y & 1) == 0) ? 0 : 3;
+ }
+
+ void clear() {
+ tile.clear();
+ quadrant.clear();
+ }
+
+ void add(TileKey tile) {
+ this.tile.add(tile);
+ this.quadrant.add(getTileQuadrant(tile));
+ }
+
+ int size() {
+ return tile.size();
+ }
+
+ TileKey getTile(int i) {
+ return tile.get(i);
+ }
+
+ int getQuadrant(int i) {
+ return quadrant.get(i);
+ }
+ }
+
+ private TileKey findFirstAvailableParentTile(int maxLevel, TileKey tile, TileTraverser traverser) {
+ traverser.clear();
+ traverser.add(tile);
+
+ TileKey parent = getParentTile(tile);
+ while (parent != null) {
+ if (parent.getLevel() <= maxLevel)
+ return parent;
+ traverser.add(parent);
+ parent = getParentTile(parent);
+ }
+ return null;
+ }
+
+ private void traverseRectangle(TileTraverser traverser, Rectangle2D rect) {
+ for (int i = traverser.size() - 1; i >= 0; --i) {
+ traverseToQuadrant(rect, traverser.getQuadrant(i));
+ }
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tile;
+
+/**
+ * @author Tuukka Lehtonen
+ *
+ * @param <O>
+ */
+public interface IFilter<O> {
+
+ public boolean select(O toTest);
+
+}
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tile;
+
+
+/**
+ * Interface for queue'ing a set of map tile requests. The tiles will be
+ * requested from the provider that has been set for the queue.
+ *
+ * @author Tuukka Lehtonen
+ */
+public interface ITileJobQueue {
+
+ /**
+ * Add a request for the specified tile with the specified listener. The
+ * listener will be notified depending on how the job progresses.
+ *
+ * @param key the requested tile
+ * @param listener the listener that will be notified of request result
+ */
+ public void addJob(TileKey key, ITileListener listener);
+
+ /**
+ * Used for removing jobs from the job queue by using the specified filter.
+ * Any not-started tile requests for which the filter returns
+ * <code>false</code> will be removed from the queue.
+ *
+ * @param filter filter that shall return <code>false</code> for tile
+ * requests to be removed from the queue
+ */
+ public void filterQueries(IFilter<TileKey> filter);
+
+ /**
+ * Sets the tile provider used by this queue to perform tile requests.
+ *
+ * @param provider the provider to use from now on
+ */
+ public void setTileProvider(ITileProvider provider);
+
+}
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tile;
+
+import java.awt.Image;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public interface ITileListener {
+
+ /**
+ * Indicates that the a request for the specified tile has been canceled for
+ * some reason.
+ *
+ * @param key the requested tile that was canceled
+ */
+ void tileCanceled(TileKey key);
+
+ /**
+ * Indicates that the a request for the specified tile has failed for some
+ * reason.
+ *
+ * @param key the requested tile
+ * @param e the reason for the failure
+ */
+ void tileFailed(TileKey key, Throwable e);
+
+ /**
+ * Indicates that the a request for the specified tile has been completed
+ * and the results delivered.
+ *
+ * @param key the requested tile
+ * @param image the result image
+ */
+ void tileUpdated(TileKey key, Image image);
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tile;
+
+import java.awt.Image;
+import java.awt.geom.Rectangle2D;
+import java.net.URI;
+
+import org.simantics.maps.ProvisionException;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public interface ITileProvider {
+
+ /**
+ * @return an identifier for the tile provider connection. May represent any
+ * protocol, not necessarily a web service at all.
+ */
+ URI getSource();
+
+ /**
+ * @return the bounding box of tiles served by this tile provider in degrees
+ * [(-180,-90) x (180,90)]
+ */
+ Rectangle2D getExtent();
+
+ /**
+ * A synchronous method for requesting the specified map tile as a raster
+ * image.
+ *
+ * @param key the requested tile
+ * @return the tile as an image
+ * @throws ProvisionException if any failure occurs while procuring the
+ * image
+ */
+ Image get(TileKey key) throws ProvisionException;
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tile;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+import org.simantics.maps.ProvisionException;
+
+/**
+ * @author Tuukka Lehtonen
+ * @deprecated this is a demo hack, do not use
+ */
+public class Shp2ImgTileProvider implements ITileProvider {
+
+ private static final String SHP2IMG_EXECUTABLE = "c:\\ms4w\\tools\\mapserv\\shp2img.exe";
+
+ private static final String SCHEME = "shp2img";
+
+ final URI source;
+
+ final File mapDirectory;
+
+ final String mapName;
+
+ final int tilePixelSize;
+
+ final String tileSizeString;
+
+ Rectangle2D extentDegrees;
+
+ Rectangle2D extentMeters;
+
+ double deWidthRecip;
+
+ double deHeightRecip;
+
+ String[] ENVP = null;
+
+ BufferedImage outOfBoundsImage;
+
+ public Shp2ImgTileProvider(String mapDirectory, String mapName, int tilePixelSize, Rectangle2D extentDegrees, Rectangle2D extentMeters) {
+ this.mapDirectory = new File(mapDirectory);
+ this.mapName = mapName;
+ this.tilePixelSize = tilePixelSize;
+ this.tileSizeString = String.valueOf(tilePixelSize);
+ this.extentDegrees = extentDegrees;
+ this.extentMeters = extentMeters;
+
+ this.deWidthRecip = 1.0 / extentDegrees.getWidth();
+ this.deHeightRecip = 1.0 / extentDegrees.getHeight();
+
+ if (!this.mapDirectory.exists()) {
+ throw new IllegalArgumentException("Map directory '" + mapDirectory + "' does not exist");
+ }
+
+ StringBuilder host = new StringBuilder();
+ boolean first = true;
+ String[] segments = mapDirectory.split(File.pathSeparator);
+ for (String seg : segments) {
+ if (!first)
+ host.append('.');
+ first = false;
+ host.append(seg);
+ }
+ host.append('.');
+ host.append(mapName);
+
+ try {
+ source = new URI(SCHEME, host.toString(), null, null);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException("Shp2ImgTileProvider: Problem in URI generation with mapDirectory="
+ + mapDirectory + ", mapName= " + mapName, e);
+ }
+
+ List<String> envp = new ArrayList<String>();
+ envp.add("GDAL_DATA=c:\\ms4w\\gdaldata");
+ envp.add("GDAL_DRIVER_PATH=c:\\ms4w\\gdalplugins");
+ envp.add("PROJ_LIB=c:\\ms4w\\proj\\nad");
+ envp.add("PATH=c:\\ms4w\\Apache\\cgi-bin;c:\\ms4w\\tools\\gdal-ogr;c:\\ms4w\\tools\\mapserv;c:\\ms4w\\tools\\shapelib;c:\\ms4w\\proj\\bin;c:\\ms4w\\tools\\shp2tile;c:\\ms4w\\tools\\shpdiff;c:\\ms4w\\tools\\avce00;c:\\ms4w\\tools\\demtools;");
+ ENVP = envp.toArray(new String[0]);
+
+// Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("png");
+// ImageReader reader = readers.next();
+ }
+
+ @Override
+ public URI getSource() {
+ return source;
+ }
+
+ @Override
+ public Rectangle2D getExtent() {
+ // FIXME: should return a clone
+ return extentDegrees;
+ }
+
+ @Override
+ public Image get(TileKey key) throws ProvisionException {
+ // TODO: make this code a utility and externalize the remaining logic as a strategy
+ // This very same code is the same in all ITileProvider implementations.
+
+ int level = key.getLevel();
+ int x = key.getX();
+ int y = key.getY();
+
+ double xTiles = Math.pow(2, level + 1);
+ double yTiles = Math.pow(2, level);
+
+ if (level < 0)
+ throw new IllegalArgumentException("invalid tile level " + level + " (tile=" + key +")");
+ if (x < 0 || x >= (int) xTiles)
+ throw new IllegalArgumentException("tile x out of bounds " + x + " (tile=" + key +")");
+ if (y < 0 || y >= (int) yTiles)
+ throw new IllegalArgumentException("tile y out of bounds " + y + " (tile=" + key +")");
+
+ Rectangle2D r = new Rectangle2D.Double();
+
+ double minx = -180;
+ double miny = -90;
+ double w = 360;
+ double h = 180;
+
+ double xdelta = w / xTiles;
+ double ydelta = h / yTiles;
+ double xx = x;
+ double yy = yTiles - y - 1;
+
+ r.setFrame(
+ minx + xdelta*xx,
+ miny + ydelta*yy,
+ xdelta,
+ ydelta
+ );
+
+ if (r.intersects(extentDegrees)) {
+ Rectangle2D meters = transformDegreesToMeters(r);
+// System.out.println("getImage: " + meters.getMinX() + ", " + meters.getMaxX() + ", " + meters.getWidth() + " ... " + meters.getMinY() + ", " + meters.getMaxY() + ", " + meters.getHeight());
+ try {
+ return getImage(meters);
+ } catch (IOException e) {
+ throw new ProvisionException(e);
+ }
+ }
+
+ return getOutOfBoundsImage();
+ }
+
+ private Image getOutOfBoundsImage() {
+ if (outOfBoundsImage != null)
+ return outOfBoundsImage;
+
+ final int outOfBoundsImageSize = 1;
+ BufferedImage image = new BufferedImage(outOfBoundsImageSize, outOfBoundsImageSize, BufferedImage.TYPE_3BYTE_BGR);
+ Graphics2D g = image.createGraphics();
+ try {
+ g.setColor(Color.PINK);
+ g.fillRect(0, 0, outOfBoundsImageSize, outOfBoundsImageSize);
+ outOfBoundsImage = image;
+ return image;
+ } finally {
+ g.dispose();
+ }
+ }
+
+ private Rectangle2D transformDegreesToMeters(Rectangle2D r) {
+ double nx1 = (r.getMinX() - extentDegrees.getMinX()) * deWidthRecip;
+ double ny1 = (r.getMinY() - extentDegrees.getMinY()) * deHeightRecip;
+ double nx2 = (r.getMaxX() - extentDegrees.getMinX()) * deWidthRecip;
+ double ny2 = (r.getMaxY() - extentDegrees.getMinY()) * deHeightRecip;
+ double dw = extentMeters.getWidth();
+ double dh = extentMeters.getHeight();
+ Rectangle2D result = new Rectangle2D.Double();
+ result.setFrameFromDiagonal(
+ extentMeters.getMinX() + nx1 * dw,
+ extentMeters.getMinY() + ny1 * dh,
+ extentMeters.getMinX() + nx2 * dw,
+ extentMeters.getMinY() + ny2 * dh
+ );
+ return result;
+ }
+
+ private BufferedImage getImage(Rectangle2D r) throws IOException {
+ double w = r.getWidth();
+ double h = r.getHeight();
+ String tileSizeStringX;
+ String tileSizeStringY;
+ if (w > h) {
+ tileSizeStringX = tileSizeString;
+ tileSizeStringY = "" + (int) Math.round(((double)tilePixelSize) * (h/w));
+ } else {
+ tileSizeStringX = "" + (int) Math.round(((double)tilePixelSize) * (w/h));
+ tileSizeStringY = tileSizeString;
+ }
+
+ File tempFile = File.createTempFile("map", ".png");
+ String[] cmd = new String[] {
+ SHP2IMG_EXECUTABLE,
+// "-all_debug",
+// "5",
+ "-m",
+ mapName,
+ "-o",
+ tempFile.getAbsolutePath(),
+ "-s",
+ tileSizeStringX,
+ tileSizeStringY,
+ "-e",
+ String.valueOf(r.getMinX()),
+ String.valueOf(r.getMinY()),
+ String.valueOf(r.getMaxX()),
+ String.valueOf(r.getMaxY())
+ };
+ try {
+ try {
+// System.out.println(System.currentTimeMillis() + " TEMP FILE: " + tempFile);
+// System.out.println(System.currentTimeMillis() + " EXEC: " + Arrays.toString(cmd));
+// System.out.println(System.currentTimeMillis() + " EXEC: " + Arrays.toString(ENVP));
+// System.out.println(System.currentTimeMillis() + " EXEC: " + mapDirectory);
+ Process p = Runtime.getRuntime().exec(cmd, ENVP, mapDirectory);
+ int result = p.waitFor();
+// System.out.println(System.currentTimeMillis() + " PROCESS EXITED: " + result);
+ if (result != 0)
+ throw new ProvisionException(SHP2IMG_EXECUTABLE + " returned error code: " + result);
+ //Thread.sleep(500);
+ BufferedImage img = ImageIO.read(tempFile);
+ //System.out.println("img = " + img);
+ if (img == null)
+ throw new ProvisionException("Failed to provide image for rectangle: " + r);
+
+ if (BufferedImage.TYPE_4BYTE_ABGR != img.getType()) {
+ BufferedImage img2 = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
+ img.copyData(img2.getRaster());
+ return img2;
+ }
+ return img;
+ } finally {
+ if (tempFile.exists()) {
+// System.out.println("DELETING TEMP FILE: " + tempFile);
+ tempFile.delete();
+ }
+ }
+ } catch (IOException e) {
+ throw new ProvisionException(e);
+ } catch (InterruptedException e) {
+ throw new ProvisionException(e);
+ }
+ }
+
+ public static void main(String[] args) {
+ try {
+ Rectangle2D degreeExtent = new Rectangle2D.Double();
+ Rectangle2D meterExtent = new Rectangle2D.Double();
+ degreeExtent.setFrameFromDiagonal(24.8, 60.175, 24.84, 60.195);
+ meterExtent.setFrameFromDiagonal(2544800, 6674000, 2546842.4, 6676000);
+
+ Shp2ImgTileProvider p = new Shp2ImgTileProvider("d:/TerrasolidOtaniemiMalli/ortho", "global.map", 512, degreeExtent, meterExtent);
+
+ BufferedImage buf = p.getImage(meterExtent);
+ ImageIO.write(buf, "png", new File("d:\\temp\\test.png"));
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tile;
+
+import java.awt.Image;
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class TileCache implements ITileListener {
+
+ public static class LevelCache {
+ /**
+ * Allow collection of tile images when under memory pressure.
+ */
+ private Map<TileKey, SoftReference<Image>> cache = new HashMap<TileKey, SoftReference<Image>>();
+
+ public Image get(TileKey key) {
+ SoftReference<Image> ref = cache.get(key);
+ if (ref == null)
+ return null;
+ return ref.get();
+ }
+
+ public void put(TileKey key, Image value) {
+ cache.put(key, new SoftReference<Image>(value));
+ }
+
+ public void remove(TileKey key) {
+ cache.remove(key);
+ }
+ }
+
+ private final ITileJobQueue job;
+ private Map<Integer, LevelCache> levels = new HashMap<Integer, LevelCache>();
+ private List<ITileListener> tileListeners = new ArrayList<ITileListener>();
+
+ public TileCache(ITileJobQueue job) {
+ this.job = job;
+ }
+
+ public void addTileListener(ITileListener l) {
+ tileListeners.add(l);
+ }
+
+ public void removeTileListener(ITileListener l) {
+ tileListeners.remove(l);
+ }
+
+ private LevelCache getLevel(int l, boolean create) {
+ synchronized (levels) {
+ LevelCache level = levels.get(l);
+ if (level == null && create) {
+ level = new LevelCache();
+ levels.put(l, level);
+ }
+ return level;
+ }
+ }
+
+ /**
+ * @param key
+ * @return <code>null</code> to signify that the requested tile was not
+ * cached and a background request has been scheduled for the tile.
+ */
+ public Image get(final TileKey key) {
+ LevelCache level = getLevel(key.getLevel(), true);
+ Image image = level.get(key);
+ if (image == null) {
+ job.addJob(key, this);
+ }
+ return image;
+ }
+
+ /**
+ * Take a peek into the cache to see if there is an image readily available
+ * for the requested tile.
+ *
+ * @param key the key of the requested tile
+ * @return <code>null</code> if there was no image in the cache
+ */
+ public Image peek(final TileKey key) {
+ LevelCache level = getLevel(key.getLevel(), false);
+ if (level == null)
+ return null;
+ synchronized (level) {
+ return level.get(key);
+ }
+ }
+
+ /**
+ * Flush the specified tile from the cache.
+ *
+ * @param key
+ */
+ public synchronized void flush(final TileKey key) {
+ LevelCache level = getLevel(key.getLevel(), false);
+ if (level != null) {
+ synchronized (level) {
+ level.remove(key);
+ }
+ }
+ }
+
+ /**
+ * Removes queries from the job queue that do not pass the specified filter.
+ */
+ public void filterJobQueue(IFilter<TileKey> filter) {
+ job.filterQueries(filter);
+ }
+
+ @Override
+ public void tileCanceled(TileKey key) {
+ for (ITileListener l : tileListeners) {
+ l.tileCanceled(key);
+ }
+ }
+
+ @Override
+ public void tileFailed(TileKey key, Throwable e) {
+ for (ITileListener l : tileListeners) {
+ l.tileFailed(key, e);
+ }
+ }
+
+ @Override
+ public void tileUpdated(TileKey key, Image image) {
+ LevelCache level = getLevel(key.getLevel(), true);
+ synchronized (level) {
+ level.put(key, image);
+ }
+
+ for (ITileListener l : tileListeners) {
+ l.tileUpdated(key, image);
+ }
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.tile;
+
+/**
+ * @author Tuukka Lehtonen
+ *
+ */
+public class TileKey {
+
+ /**
+ * Valid values within {0, 1, 2,... , n}. When the value is 0, the only
+ * valid (x,y) values are in {(0,0), (1,0)}, meaning half of the whole
+ * globe. Every time the value of level increases by one, the map is
+ * subdivided by 4 in the x direction and by two in the y direction, i.e.
+ * into eight equal size parts.
+ */
+ private final int level;
+
+ private final int x;
+
+ private final int y;
+
+ private final int hash;
+
+ public TileKey(int level, int x, int y) {
+ this.level = level;
+ this.x = x;
+ this.y = y;
+ this.hash = hash();
+ }
+
+ private int hash() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + level;
+ result = prime * result + x;
+ result = prime * result + y;
+ return result;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ final TileKey other = (TileKey) obj;
+ if (level != other.level)
+ return false;
+ if (x != other.x)
+ return false;
+ if (y != other.y)
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "TileKey [level=" + level + ", x=" + x + ", y=" + y + "]";
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.wms;
+
+/**
+ * Represents a failure in web service execution.
+ *
+ * @author Tuukka Lehtonen
+ */
+public class ServiceException extends RuntimeException {
+
+ private static final long serialVersionUID = 6031083247533593501L;
+
+ public ServiceException() {
+ super();
+ }
+
+ public ServiceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ServiceException(String message) {
+ super(message);
+ }
+
+ public ServiceException(Throwable cause) {
+ super(cause);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.wms;
+
+import java.awt.geom.Rectangle2D;
+import java.net.MalformedURLException;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class WMSGetMapQuery {
+
+ String srs = "EPSG:4326";
+
+ int width;
+
+ int height;
+
+ Rectangle2D bbox;
+
+ String[] styles;
+
+ String[] layers;
+
+ String format;
+
+ public WMSGetMapQuery(int width, int height, Rectangle2D bbox, String format, String... layers) throws MalformedURLException {
+ super();
+ this.width = width;
+ this.height = height;
+ this.bbox = bbox;
+ this.format = format;
+ this.layers = layers;
+ }
+
+ public void setSpatialReferenceSystem(String srs) {
+ this.srs = srs;
+ }
+
+ public void setStyles(String... styles) {
+ this.styles = styles;
+ }
+
+ public void setLayers(String... layers) {
+ this.layers = layers;
+ }
+
+ public String toString() {
+ if (layers.length == 0)
+ throw new IllegalStateException("0 layers defined");
+
+ StringBuilder sb = new StringBuilder(200);
+
+ // request=GetMap&layers=global_mosaic&srs=EPSG:4326&width=512&height=512
+ // &bbox=-180,-38,-52,90
+ // &format=image/jpeg&version=1.1.1&styles=visual
+
+ sb.append("request=GetMap");
+ sb.append("&service=WMS");
+ sb.append("&layers=");
+ sb.append(layers[0]);
+ for (int i = 1; i < layers.length; ++i) {
+ sb.append(',');
+ sb.append(layers[i]);
+ }
+ sb.append("&srs=");
+ sb.append(srs);
+ sb.append("&width=");
+ sb.append(width);
+ sb.append("&height=");
+ sb.append(height);
+ sb.append("&bbox=");
+ sb.append(bbox.getMinX());
+ sb.append(',');
+ sb.append(bbox.getMinY());
+ sb.append(',');
+ sb.append(bbox.getMaxX());
+ sb.append(',');
+ sb.append(bbox.getMaxY());
+ sb.append("&format=");
+ sb.append(format);
+ sb.append("&version=1.1.1");
+ sb.append("&styles=");
+ if (styles != null && styles.length > 0) {
+ sb.append(styles[0]);
+ for (int i = 1; i < layers.length; ++i) {
+ sb.append(',');
+ sb.append(layers[i]);
+ }
+ }
+
+ return sb.toString();
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.maps.wms;
+
+import java.awt.Dimension;
+import java.awt.Image;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+
+import org.simantics.maps.ProvisionException;
+import org.simantics.maps.WebService;
+import org.simantics.maps.tile.ITileProvider;
+import org.simantics.maps.tile.TileKey;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class WMSTileProvider implements ITileProvider {
+
+ private WebService service;
+
+ private int tileSize;
+
+ private String[] layers;
+
+ public WMSTileProvider(WebService service, int tileSize, String... layers) {
+ this.service = service;
+ this.tileSize = tileSize;
+ this.layers = layers;
+ }
+
+ @Override
+ public URI getSource() {
+ return service.getURI();
+ }
+
+ @Override
+ public Rectangle2D getExtent() {
+ return new Rectangle2D.Double(-180, -90, 360, 180);
+ }
+
+ @Override
+ public Image get(TileKey key) throws ProvisionException {
+ int level = key.getLevel();
+ int x = key.getX();
+ int y = key.getY();
+
+ double xTiles = Math.pow(2, level + 1);
+ double yTiles = Math.pow(2, level);
+
+ if (level < 0)
+ throw new IllegalArgumentException("invalid tile level " + level + " (tile=" + key +")");
+ if (x < 0 || x >= (int) xTiles)
+ throw new IllegalArgumentException("tile x out of bounds " + x + " (tile=" + key +")");
+ if (y < 0 || y >= (int) yTiles)
+ throw new IllegalArgumentException("tile y out of bounds " + y + " (tile=" + key +")");
+
+ try {
+ Rectangle2D r = new Rectangle2D.Double();
+
+ double minx = -180;
+ double miny = -90;
+ double w = 360;
+ double h = 180;
+
+ double xdelta = w / xTiles;
+ double ydelta = h / yTiles;
+ double xx = x;
+ double yy = yTiles - y - 1;
+
+ r.setFrame(
+ minx + xdelta*xx,
+ miny + ydelta*yy,
+ xdelta,
+ ydelta
+ );
+
+// System.out.println("TileProvider: requesting " + key + ", bbox=" + r + ", size=" + tileSize);
+ BufferedImage img = getMap(service, tileSize, tileSize, r, "image/jpeg", layers);
+// System.out.println("TileProvider: response: " + img);
+ return img;
+ } catch (MalformedURLException e) {
+ throw new ProvisionException(e);
+ } catch (IOException e) {
+ throw new ProvisionException(e);
+ }
+ }
+
+ /**
+ * Get a bufferedimage of the map data for the geographical coordinates.
+ *
+ * @param x
+ * @param y
+ * @param z
+ * @return
+ * @throws MalformedURLException
+ */
+ public static BufferedImage getMap(WebService service, Dimension rasterSize, Rectangle2D bbox, String format, String... layers) throws MalformedURLException, IOException {
+ return getMap(service, rasterSize.width, rasterSize.height, bbox, format, layers);
+ }
+
+ /**
+ * Get a bufferedimage of the map data for the geographical coordinates.
+ *
+ * @param x
+ * @param y
+ * @param z
+ * @return
+ * @throws MalformedURLException
+ */
+ public static BufferedImage getMap(WebService service, int rasterWidth, int rasterHeight, Rectangle2D bbox, String format, String... layers) throws MalformedURLException, IOException {
+ if (rasterWidth == 0 || rasterHeight == 0)
+ return new BufferedImage(0, 0, BufferedImage.TYPE_INT_ARGB);
+
+ // Create a method instance.
+ WMSGetMapQuery req = new WMSGetMapQuery(rasterWidth, rasterHeight, bbox, format, layers);
+// req.setStyles("visual");
+
+// String requestUrl = service.getRequestURL("", req.toString());
+// System.out.println("REQUEST: " + requestUrl);
+
+ HttpURLConnection connection = null;
+ try {
+ connection = service.openConnection("", req.toString());
+ // Execute the method.
+ int statusCode = connection.getResponseCode();
+
+ if (statusCode != HttpURLConnection.HTTP_OK) {
+ System.err.println("Method failed: " + connection.getResponseMessage());
+ }
+
+// for (Header h : method.getResponseHeaders()) {
+// System.out.println("HDR: " + h.getName() + ": " + h.getValue());
+// }
+
+ String contentType = connection.getHeaderField("Content-Type");
+ if (contentType != null) {
+// System.out.println("Content-Type: " + contentType);
+ if (contentType.equals(format)) {
+ InputStream response = connection.getInputStream();
+ BufferedImage img = ImageIO.read(response);
+ if (img == null) {
+ throw new IIOException("ImageIO returned null, unable to decode stream as image data.");
+ }
+ return img;
+ }
+ }
+
+ // Request failed, produce as much debug information as possible.
+ StringBuilder sb = new StringBuilder();
+ for (String h : connection.getHeaderFields().keySet()) {
+ sb.append(h);
+ sb.append(": ");
+ sb.append(connection.getHeaderField(h));
+ sb.append("\n");
+ }
+ String response = connection.getContent().toString();
+ sb.append("Response body:\n");
+ sb.append(response);
+ throw new ServiceException("WMS GetMap request failed: " + connection.getURL().toString() + "\n" + sb.toString());
+ } finally {
+ // Release the connection.
+ connection.disconnect();
+ }
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.simantics.district.network.ui.ontology</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.simantics.graph.builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.simantics.graph.nature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Ontology
+Bundle-SymbolicName: org.simantics.district.network.ui.ontology
+Bundle-Version: 1.0.0.qualifier
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Require-Bundle: org.simantics.district.network.ontology;bundle-version="1.0.0",
+ org.simantics.layer0;bundle-version="1.1.0",
+ org.simantics.viewpoint.ontology;bundle-version="1.2.0",
+ org.simantics.selectionview.ontology;bundle-version="1.2.0",
+ org.simantics.selectionview.ui.ontology;bundle-version="1.1.0",
+ org.simantics.views.ontology;bundle-version="1.2.0"
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ graph.tg,\
+ adapters.xml
--- /dev/null
+L0 = <http://www.simantics.org/Layer0-1.1>
+VP = <http://www.simantics.org/Viewpoint-1.2>
+VIEW = <http://www.simantics.org/Views-1.2>
+SEL = <http://www.simantics.org/SelectionView-1.2>
+SEL_UI = <http://www.simantics.org/SelectionViewUI-1.1>
+DN = <http://www.simantics.org/DistrictNetwork-1.0>
+
+DNUI = <http://www.simantics.org/DistrictNetworkUI-1.0> : L0.Ontology
+ @L0.new
+ L0.Ontology.global true
+ L0.HasResourceClass "org.simantics.district.network.ui.ontology.DistrictNetworkUIResource"
+
+DNUI.SelectionTabContribution : SEL.MultiTypedVariableTabContribution
+ SEL.TypedTabContribution.HasType DN.Vertex
+ SEL.TypedTabContribution.HasType DN.Edge
+ SEL.TypedTabContribution.HasType DN.Diagram
+ SEL.VariableTabContribution.HasView SEL_UI.StandardProperties
+ SEL.TabContribution.HasPriority 10
+ L0.HasLabel "Network Properties"
+
+DNUI.NetworkProperties : SEL_UI.StandardProperties
+ @L0.assert VIEW.Explorer.browseContext
+ _ : VIEW.ResourceURI
+ VIEW.ResourceURI.HasResource DN.BrowseContext
+
+DNUI.SelectionTab : VIEW.Composite
+ VIEW.Composite.layout _ : VIEW.GridLayout
+ VIEW.GridLayout.columnCount 5
+ VIEW.Control.layoutData _ : VIEW.GridLayout.GridData
+ VIEW.GridLayout.GridData.horizontalGrab true
+ VIEW.GridLayout.GridData.verticalGrab true
--- /dev/null
+package org.simantics.district.network.ui.ontology;
+
+import org.simantics.db.RequestProcessor;
+import org.simantics.db.Resource;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.request.Read;
+import org.simantics.db.Session;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.service.QueryControl;
+
+public class DistrictNetworkUIResource {
+
+ public final Resource NetworkProperties;
+ public final Resource SelectionTab;
+ public final Resource SelectionTabContribution;
+
+ public static class URIs {
+ public static final String NetworkProperties = "http://www.simantics.org/DistrictNetworkUI-1.0/NetworkProperties";
+ public static final String SelectionTab = "http://www.simantics.org/DistrictNetworkUI-1.0/SelectionTab";
+ public static final String SelectionTabContribution = "http://www.simantics.org/DistrictNetworkUI-1.0/SelectionTabContribution";
+ }
+
+ public static Resource getResourceOrNull(ReadGraph graph, String uri) {
+ try {
+ return graph.getResource(uri);
+ } catch(DatabaseException e) {
+ System.err.println(e.getMessage());
+ return null;
+ }
+ }
+
+ public DistrictNetworkUIResource(ReadGraph graph) {
+ NetworkProperties = getResourceOrNull(graph, URIs.NetworkProperties);
+ SelectionTab = getResourceOrNull(graph, URIs.SelectionTab);
+ SelectionTabContribution = getResourceOrNull(graph, URIs.SelectionTabContribution);
+ }
+
+ public static DistrictNetworkUIResource getInstance(ReadGraph graph) {
+ Session session = graph.getSession();
+ DistrictNetworkUIResource ret = session.peekService(DistrictNetworkUIResource.class);
+ if(ret == null) {
+ QueryControl qc = graph.getService(QueryControl.class);
+ ret = new DistrictNetworkUIResource(qc.getIndependentGraph(graph));
+ session.registerService(DistrictNetworkUIResource.class, ret);
+ }
+ return ret;
+ }
+
+ public static DistrictNetworkUIResource getInstance(RequestProcessor session) throws DatabaseException {
+ DistrictNetworkUIResource ret = session.peekService(DistrictNetworkUIResource.class);
+ if(ret == null) {
+ ret = session.syncRequest(new Read<DistrictNetworkUIResource>() {
+ public DistrictNetworkUIResource perform(ReadGraph graph) throws DatabaseException {
+ QueryControl qc = graph.getService(QueryControl.class);
+ return new DistrictNetworkUIResource(qc.getIndependentGraph(graph));
+ }
+ });
+ session.registerService(DistrictNetworkUIResource.class, ret);
+ }
+ return ret;
+ }
+
+}
+