]> gerrit.simantics Code Review - simantics/district.git/commitdiff
Share some projects for Simantics District 04/304/1
authorjsimomaa <jani.simomaa@gmail.com>
Wed, 1 Feb 2017 09:40:42 +0000 (11:40 +0200)
committerjsimomaa <jani.simomaa@gmail.com>
Wed, 1 Feb 2017 09:40:42 +0000 (11:40 +0200)
refs #6958

Change-Id: If9cb332a4bea53da2960da9343c6939acd1c177a

44 files changed:
org.simantics.district.maps/.classpath [new file with mode: 0644]
org.simantics.district.maps/.project [new file with mode: 0644]
org.simantics.district.maps/META-INF/MANIFEST.MF [new file with mode: 0644]
org.simantics.district.maps/build.properties [new file with mode: 0644]
org.simantics.district.maps/build.xml [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/IProxyUtil.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/ProvisionException.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/WebService.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/debug/DebugTileProvider.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/eclipse/DiskCachingTileProvider.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/eclipse/EclipseProxyUtil.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/eclipse/TileJob.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/eclipse/TileJobQueue.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/osm/OSMGetMapQuery.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/osm/OSMTileProvider.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/pojo/AppletProxyUtil.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/pojo/TileJob.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/pojo/TileJobQueue.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/query/IQueryListener.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/query/Query.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/sg/MapNode.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tests/View.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest1.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest2.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest3.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tile/IFilter.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tile/ITileJobQueue.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tile/ITileListener.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tile/ITileProvider.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tile/Shp2ImgTileProvider.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tile/TileCache.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/tile/TileKey.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/wms/ServiceException.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/wms/WMSGetMapQuery.java [new file with mode: 0644]
org.simantics.district.maps/src/org/simantics/maps/wms/WMSTileProvider.java [new file with mode: 0644]
org.simantics.district.network.ui.ontology/.classpath [new file with mode: 0644]
org.simantics.district.network.ui.ontology/.project [new file with mode: 0644]
org.simantics.district.network.ui.ontology/META-INF/MANIFEST.MF [new file with mode: 0644]
org.simantics.district.network.ui.ontology/build.properties [new file with mode: 0644]
org.simantics.district.network.ui.ontology/graph.tg [new file with mode: 0644]
org.simantics.district.network.ui.ontology/graph/DistrictNetworkUI.pgraph [new file with mode: 0644]
org.simantics.district.network.ui.ontology/src/org/simantics/district/network/ui/ontology/DistrictNetworkUIResource.java [new file with mode: 0644]

diff --git a/org.simantics.district.maps/.classpath b/org.simantics.district.maps/.classpath
new file mode 100644 (file)
index 0000000..751c8f2
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/org.simantics.district.maps/.project b/org.simantics.district.maps/.project
new file mode 100644 (file)
index 0000000..d74d8ce
--- /dev/null
@@ -0,0 +1,28 @@
+<?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>
diff --git a/org.simantics.district.maps/META-INF/MANIFEST.MF b/org.simantics.district.maps/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..a5c4a10
--- /dev/null
@@ -0,0 +1,21 @@
+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
diff --git a/org.simantics.district.maps/build.properties b/org.simantics.district.maps/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.simantics.district.maps/build.xml b/org.simantics.district.maps/build.xml
new file mode 100644 (file)
index 0000000..5692f0c
--- /dev/null
@@ -0,0 +1,42 @@
+<?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>
diff --git a/org.simantics.district.maps/src/org/simantics/maps/IProxyUtil.java b/org.simantics.district.maps/src/org/simantics/maps/IProxyUtil.java
new file mode 100644 (file)
index 0000000..cbb4e3f
--- /dev/null
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * 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);
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/ProvisionException.java b/org.simantics.district.maps/src/org/simantics/maps/ProvisionException.java
new file mode 100644 (file)
index 0000000..33db0c6
--- /dev/null
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * 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);
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/WebService.java b/org.simantics.district.maps/src/org/simantics/maps/WebService.java
new file mode 100644 (file)
index 0000000..f515d47
--- /dev/null
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * 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;
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/debug/DebugTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/debug/DebugTileProvider.java
new file mode 100644 (file)
index 0000000..bb868ff
--- /dev/null
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * 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);
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/eclipse/DiskCachingTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/eclipse/DiskCachingTileProvider.java
new file mode 100644 (file)
index 0000000..2bef3ee
--- /dev/null
@@ -0,0 +1,191 @@
+/*******************************************************************************
+ * 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() + "]";
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/eclipse/EclipseProxyUtil.java b/org.simantics.district.maps/src/org/simantics/maps/eclipse/EclipseProxyUtil.java
new file mode 100644 (file)
index 0000000..601a82f
--- /dev/null
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * 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;
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java b/org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java
new file mode 100644 (file)
index 0000000..3c4a994
--- /dev/null
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * 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);
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/eclipse/TileJob.java b/org.simantics.district.maps/src/org/simantics/maps/eclipse/TileJob.java
new file mode 100644 (file)
index 0000000..e73c6a5
--- /dev/null
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * 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;
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/eclipse/TileJobQueue.java b/org.simantics.district.maps/src/org/simantics/maps/eclipse/TileJobQueue.java
new file mode 100644 (file)
index 0000000..3676b8d
--- /dev/null
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * 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());
+                }
+            }
+        }
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/osm/OSMGetMapQuery.java b/org.simantics.district.maps/src/org/simantics/maps/osm/OSMGetMapQuery.java
new file mode 100644 (file)
index 0000000..02dd42f
--- /dev/null
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * 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);
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/osm/OSMTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/osm/OSMTileProvider.java
new file mode 100644 (file)
index 0000000..2b29d20
--- /dev/null
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * 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();
+        }
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/pojo/AppletProxyUtil.java b/org.simantics.district.maps/src/org/simantics/maps/pojo/AppletProxyUtil.java
new file mode 100644 (file)
index 0000000..1234bd4
--- /dev/null
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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;
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/pojo/TileJob.java b/org.simantics.district.maps/src/org/simantics/maps/pojo/TileJob.java
new file mode 100644 (file)
index 0000000..6ed930d
--- /dev/null
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * 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;
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/pojo/TileJobQueue.java b/org.simantics.district.maps/src/org/simantics/maps/pojo/TileJobQueue.java
new file mode 100644 (file)
index 0000000..f6ab637
--- /dev/null
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * 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);
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/query/IQueryListener.java b/org.simantics.district.maps/src/org/simantics/maps/query/IQueryListener.java
new file mode 100644 (file)
index 0000000..65339e6
--- /dev/null
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * 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
diff --git a/org.simantics.district.maps/src/org/simantics/maps/query/Query.java b/org.simantics.district.maps/src/org/simantics/maps/query/Query.java
new file mode 100644 (file)
index 0000000..7f1bf79
--- /dev/null
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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 + "]";
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/sg/MapNode.java b/org.simantics.district.maps/src/org/simantics/maps/sg/MapNode.java
new file mode 100644 (file)
index 0000000..01eab3a
--- /dev/null
@@ -0,0 +1,583 @@
+/*******************************************************************************
+ * 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
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tests/View.java b/org.simantics.district.maps/src/org/simantics/maps/tests/View.java
new file mode 100644 (file)
index 0000000..cf272c4
--- /dev/null
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * 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
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest1.java b/org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest1.java
new file mode 100644 (file)
index 0000000..cec0f78
--- /dev/null
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * 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
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest2.java b/org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest2.java
new file mode 100644 (file)
index 0000000..39bf09d
--- /dev/null
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * 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
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest3.java b/org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest3.java
new file mode 100644 (file)
index 0000000..3b1e439
--- /dev/null
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * 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
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java
new file mode 100644 (file)
index 0000000..10abef0
--- /dev/null
@@ -0,0 +1,284 @@
+/*******************************************************************************
+ * 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));
+        }
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tile/IFilter.java b/org.simantics.district.maps/src/org/simantics/maps/tile/IFilter.java
new file mode 100644 (file)
index 0000000..50888de
--- /dev/null
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * 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
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tile/ITileJobQueue.java b/org.simantics.district.maps/src/org/simantics/maps/tile/ITileJobQueue.java
new file mode 100644 (file)
index 0000000..7a0a375
--- /dev/null
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * 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
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tile/ITileListener.java b/org.simantics.district.maps/src/org/simantics/maps/tile/ITileListener.java
new file mode 100644 (file)
index 0000000..ac267ec
--- /dev/null
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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);
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tile/ITileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/tile/ITileProvider.java
new file mode 100644 (file)
index 0000000..f88b8ab
--- /dev/null
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * 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;
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tile/Shp2ImgTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/tile/Shp2ImgTileProvider.java
new file mode 100644 (file)
index 0000000..0431e6f
--- /dev/null
@@ -0,0 +1,286 @@
+/*******************************************************************************
+ * 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();
+        }
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tile/TileCache.java b/org.simantics.district.maps/src/org/simantics/maps/tile/TileCache.java
new file mode 100644 (file)
index 0000000..9f58193
--- /dev/null
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * 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);
+        }
+       }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/tile/TileKey.java b/org.simantics.district.maps/src/org/simantics/maps/tile/TileKey.java
new file mode 100644 (file)
index 0000000..b50a262
--- /dev/null
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * 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 + "]";
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/wms/ServiceException.java b/org.simantics.district.maps/src/org/simantics/maps/wms/ServiceException.java
new file mode 100644 (file)
index 0000000..35234c0
--- /dev/null
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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);
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/wms/WMSGetMapQuery.java b/org.simantics.district.maps/src/org/simantics/maps/wms/WMSGetMapQuery.java
new file mode 100644 (file)
index 0000000..dc65a53
--- /dev/null
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * 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();
+    }
+
+}
diff --git a/org.simantics.district.maps/src/org/simantics/maps/wms/WMSTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/wms/WMSTileProvider.java
new file mode 100644 (file)
index 0000000..60e0206
--- /dev/null
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * 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();
+        }
+    }
+
+}
diff --git a/org.simantics.district.network.ui.ontology/.classpath b/org.simantics.district.network.ui.ontology/.classpath
new file mode 100644 (file)
index 0000000..eca7bdb
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/org.simantics.district.network.ui.ontology/.project b/org.simantics.district.network.ui.ontology/.project
new file mode 100644 (file)
index 0000000..b6ba825
--- /dev/null
@@ -0,0 +1,34 @@
+<?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>
diff --git a/org.simantics.district.network.ui.ontology/META-INF/MANIFEST.MF b/org.simantics.district.network.ui.ontology/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..1f9cbea
--- /dev/null
@@ -0,0 +1,12 @@
+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"
diff --git a/org.simantics.district.network.ui.ontology/build.properties b/org.simantics.district.network.ui.ontology/build.properties
new file mode 100644 (file)
index 0000000..557c791
--- /dev/null
@@ -0,0 +1,6 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               graph.tg,\
+               adapters.xml
diff --git a/org.simantics.district.network.ui.ontology/graph.tg b/org.simantics.district.network.ui.ontology/graph.tg
new file mode 100644 (file)
index 0000000..a8b273c
Binary files /dev/null and b/org.simantics.district.network.ui.ontology/graph.tg differ
diff --git a/org.simantics.district.network.ui.ontology/graph/DistrictNetworkUI.pgraph b/org.simantics.district.network.ui.ontology/graph/DistrictNetworkUI.pgraph
new file mode 100644 (file)
index 0000000..c03c89c
--- /dev/null
@@ -0,0 +1,31 @@
+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
diff --git a/org.simantics.district.network.ui.ontology/src/org/simantics/district/network/ui/ontology/DistrictNetworkUIResource.java b/org.simantics.district.network.ui.ontology/src/org/simantics/district/network/ui/ontology/DistrictNetworkUIResource.java
new file mode 100644 (file)
index 0000000..d6ae2f6
--- /dev/null
@@ -0,0 +1,64 @@
+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;
+    }
+    
+}
+