From e9f74f09e0cedb603c0b4de9e542de8dd64a5ce3 Mon Sep 17 00:00:00 2001 From: jsimomaa Date: Wed, 1 Feb 2017 11:40:42 +0200 Subject: [PATCH] Share some projects for Simantics District refs #6958 Change-Id: If9cb332a4bea53da2960da9343c6939acd1c177a --- org.simantics.district.maps/.classpath | 7 + org.simantics.district.maps/.project | 28 + .../META-INF/MANIFEST.MF | 21 + org.simantics.district.maps/build.properties | 4 + org.simantics.district.maps/build.xml | 42 ++ .../src/org/simantics/maps/IProxyUtil.java | 23 + .../simantics/maps/ProvisionException.java | 37 ++ .../src/org/simantics/maps/WebService.java | 90 +++ .../maps/debug/DebugTileProvider.java | 133 ++++ .../maps/eclipse/DiskCachingTileProvider.java | 191 ++++++ .../maps/eclipse/EclipseProxyUtil.java | 54 ++ .../simantics/maps/eclipse/MapPainter.java | 132 ++++ .../org/simantics/maps/eclipse/TileJob.java | 70 +++ .../simantics/maps/eclipse/TileJobQueue.java | 96 +++ .../simantics/maps/osm/OSMGetMapQuery.java | 72 +++ .../simantics/maps/osm/OSMTileProvider.java | 163 +++++ .../simantics/maps/pojo/AppletProxyUtil.java | 48 ++ .../src/org/simantics/maps/pojo/TileJob.java | 115 ++++ .../org/simantics/maps/pojo/TileJobQueue.java | 76 +++ .../simantics/maps/query/IQueryListener.java | 27 + .../src/org/simantics/maps/query/Query.java | 39 ++ .../src/org/simantics/maps/sg/MapNode.java | 583 ++++++++++++++++++ .../src/org/simantics/maps/tests/View.java | 70 +++ .../org/simantics/maps/tests/WMSTest1.java | 71 +++ .../org/simantics/maps/tests/WMSTest2.java | 162 +++++ .../org/simantics/maps/tests/WMSTest3.java | 161 +++++ .../maps/tile/CompoundTileProvider.java | 284 +++++++++ .../src/org/simantics/maps/tile/IFilter.java | 23 + .../simantics/maps/tile/ITileJobQueue.java | 49 ++ .../simantics/maps/tile/ITileListener.java | 47 ++ .../simantics/maps/tile/ITileProvider.java | 48 ++ .../maps/tile/Shp2ImgTileProvider.java | 286 +++++++++ .../org/simantics/maps/tile/TileCache.java | 152 +++++ .../src/org/simantics/maps/tile/TileKey.java | 91 +++ .../simantics/maps/wms/ServiceException.java | 39 ++ .../simantics/maps/wms/WMSGetMapQuery.java | 104 ++++ .../simantics/maps/wms/WMSTileProvider.java | 184 ++++++ .../.classpath | 7 + .../.project | 34 + .../META-INF/MANIFEST.MF | 12 + .../build.properties | 6 + .../graph.tg | Bin 0 -> 2504 bytes .../graph/DistrictNetworkUI.pgraph | 31 + .../ontology/DistrictNetworkUIResource.java | 64 ++ 44 files changed, 3976 insertions(+) create mode 100644 org.simantics.district.maps/.classpath create mode 100644 org.simantics.district.maps/.project create mode 100644 org.simantics.district.maps/META-INF/MANIFEST.MF create mode 100644 org.simantics.district.maps/build.properties create mode 100644 org.simantics.district.maps/build.xml create mode 100644 org.simantics.district.maps/src/org/simantics/maps/IProxyUtil.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/ProvisionException.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/WebService.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/debug/DebugTileProvider.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/eclipse/DiskCachingTileProvider.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/eclipse/EclipseProxyUtil.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/eclipse/TileJob.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/eclipse/TileJobQueue.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/osm/OSMGetMapQuery.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/osm/OSMTileProvider.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/pojo/AppletProxyUtil.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/pojo/TileJob.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/pojo/TileJobQueue.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/query/IQueryListener.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/query/Query.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/sg/MapNode.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tests/View.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest1.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest2.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest3.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tile/IFilter.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tile/ITileJobQueue.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tile/ITileListener.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tile/ITileProvider.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tile/Shp2ImgTileProvider.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tile/TileCache.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/tile/TileKey.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/wms/ServiceException.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/wms/WMSGetMapQuery.java create mode 100644 org.simantics.district.maps/src/org/simantics/maps/wms/WMSTileProvider.java create mode 100644 org.simantics.district.network.ui.ontology/.classpath create mode 100644 org.simantics.district.network.ui.ontology/.project create mode 100644 org.simantics.district.network.ui.ontology/META-INF/MANIFEST.MF create mode 100644 org.simantics.district.network.ui.ontology/build.properties create mode 100644 org.simantics.district.network.ui.ontology/graph.tg create mode 100644 org.simantics.district.network.ui.ontology/graph/DistrictNetworkUI.pgraph create mode 100644 org.simantics.district.network.ui.ontology/src/org/simantics/district/network/ui/ontology/DistrictNetworkUIResource.java diff --git a/org.simantics.district.maps/.classpath b/org.simantics.district.maps/.classpath new file mode 100644 index 00000000..751c8f2e --- /dev/null +++ b/org.simantics.district.maps/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.simantics.district.maps/.project b/org.simantics.district.maps/.project new file mode 100644 index 00000000..d74d8ce3 --- /dev/null +++ b/org.simantics.district.maps/.project @@ -0,0 +1,28 @@ + + + org.simantics.district.maps + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.simantics.district.maps/META-INF/MANIFEST.MF b/org.simantics.district.maps/META-INF/MANIFEST.MF new file mode 100644 index 00000000..a5c4a105 --- /dev/null +++ b/org.simantics.district.maps/META-INF/MANIFEST.MF @@ -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 index 00000000..34d2e4d2 --- /dev/null +++ b/org.simantics.district.maps/build.properties @@ -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 index 00000000..5692f0cf --- /dev/null +++ b/org.simantics.district.maps/build.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 index 00000000..cbb4e3f5 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/IProxyUtil.java @@ -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 index 00000000..33db0c60 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/ProvisionException.java @@ -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 index 00000000..f515d470 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/WebService.java @@ -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 index 00000000..bb868ffd --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/debug/DebugTileProvider.java @@ -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 index 00000000..2bef3eeb --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/eclipse/DiskCachingTileProvider.java @@ -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 writers = ImageIO.getImageWritersByFormatName(CACHED_IMAGE_FORMAT); +// writer = writers.next(); +// Iterator 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 index 00000000..601a82f7 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/eclipse/EclipseProxyUtil.java @@ -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 null 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 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 index 00000000..3c4a9944 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/eclipse/MapPainter.java @@ -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 index 00000000..e73c6a54 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/eclipse/TileJob.java @@ -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 query; + + public TileJob(Query query, ITileProvider provider) { + super("Raster Map Request Job"); + assert(query != null); + this.query = query; + this.provider = provider; + } + + public Query 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 index 00000000..3676b8d9 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/eclipse/TileJobQueue.java @@ -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 queries = new Hashtable(); + + public TileJobQueue() { + } + + public void setTileProvider(ITileProvider provider) { + this.provider = provider; + } + + public void addJob(Query job) { + TileJob tj = new TileJob(job, provider); + queries.put(job.source, tj); + tj.schedule(); + } + + public void addAsFirstJob(Query job) { + TileJob tj = new TileJob(job, provider); + queries.put(job.source, tj); + tj.schedule(); + } + + public void removeJob(Query 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(key, new IQueryListener() { + @Override + public void queryCanceled(Query job) { + listener.tileCanceled(key); + } + @Override + public void queryFailed(Query job, Exception error) { + listener.tileFailed(key, error); + } + @Override + public void queryComplete(Query job, Image result) { + listener.tileUpdated(key, result); + } + })); + } + + @Override + public void filterQueries(IFilter filter) { + Set keys = new HashSet(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 index 00000000..02dd42f9 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/osm/OSMGetMapQuery.java @@ -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 index 00000000..2b29d20f --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/osm/OSMTileProvider.java @@ -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 index 00000000..1234bd42 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/pojo/AppletProxyUtil.java @@ -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 null if the + * service is not available. + */ + public Proxy getProxyService(final String url) { + List 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 index 00000000..6ed930d4 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/pojo/TileJob.java @@ -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> queue = new LinkedList>(); + 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 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 pullNextJob() { + if (queue.isEmpty()) + return null; + return queue.removeFirst(); + } + + public synchronized void clear() { + @SuppressWarnings("unchecked") + Query jobs[] = queue.toArray(new Query[queue.size()]); + for (Query j : jobs) + removeJob(j); + } + + public synchronized void addJob(Query job) { + queue.addLast(job); + if (queue.size() == 1) { + executor.execute(this); + } + } + + public synchronized void addAsFirstJob(Query job) { + queue.addFirst(job); + if (queue.size() == 1) { + executor.execute(this); + } + } + + public synchronized boolean removeJob(Query job) { + if (queue.remove(job)) { + job.listener.queryCanceled(job); + return true; + } + return false; + } + + public synchronized void filterQueries(IFilter filter) { + if (queue.isEmpty()) + return; + + LinkedList> result = new LinkedList>(); + for (Query 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 index 00000000..f6ab637c --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/pojo/TileJobQueue.java @@ -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(key, new IQueryListener() { + @Override + public void queryCanceled(Query job) { + listener.tileCanceled(key); + } + @Override + public void queryFailed(Query job, Exception error) { + listener.tileFailed(key, error); + } + @Override + public void queryComplete(Query job, Image result) { + listener.tileUpdated(key, result); + } + })); + } + + @Override + public void filterQueries(IFilter filter) { + getJob().filterQueries(filter); + } + + public void addAsFirstJob(Query job) { + getJob().addAsFirstJob(job); + } + + public void removeJob(Query 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 index 00000000..65339e6f --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/query/IQueryListener.java @@ -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 + * @param + */ +public interface IQueryListener { + + void queryComplete(Query job, R result); + + void queryFailed(Query job, Exception error); + + void queryCanceled(Query 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 index 00000000..7f1bf79c --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/query/Query.java @@ -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 + * @param + */ +public class Query { + + public final S source; + + public final IQueryListener listener; + + public Query(S source, IQueryListener 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 index 00000000..01eab3a3 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/MapNode.java @@ -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 { + private static final long serialVersionUID = 5743763238677223952L; + } + + static class TileTraverser { + List tile = new ArrayList(4); + List quadrant = new ArrayList(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 + { + private T o = null; + public T get() { + return o; + } + public void set(T o) { + this.o = o; + } + } + + public final class Pair { + 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 neededTiles = new HashMap(); + + private TileTraverser tileTraverser = new TileTraverser(); + + private ITileJobQueue job = null; + + private Map tileStates = new Hashtable(); + private ObjectHolder notLoadedImage = new ObjectHolder(); + private ObjectHolder loadingImage = new ObjectHolder(); + private ObjectHolder notAvailableImage = new ObjectHolder(); + + 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<= 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 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 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() { + @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 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(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 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 index 00000000..cf272c4c --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tests/View.java @@ -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 index 00000000..cec0f789 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest1.java @@ -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 index 00000000..39bf09d5 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest2.java @@ -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 index 00000000..3b1e4396 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tests/WMSTest3.java @@ -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 index 00000000..10abef03 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java @@ -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 providers = new ArrayList(); + + 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 tile = new ArrayList(4); + List quadrant = new ArrayList(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 index 00000000..50888def --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tile/IFilter.java @@ -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 + */ +public interface IFilter { + + 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 index 00000000..7a0a3753 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tile/ITileJobQueue.java @@ -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 + * false will be removed from the queue. + * + * @param filter filter that shall return false for tile + * requests to be removed from the queue + */ + public void filterQueries(IFilter 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 index 00000000..ac267ec4 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tile/ITileListener.java @@ -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 index 00000000..f88b8ab5 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tile/ITileProvider.java @@ -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 index 00000000..0431e6fe --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tile/Shp2ImgTileProvider.java @@ -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 envp = new ArrayList(); + 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 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 index 00000000..9f581930 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tile/TileCache.java @@ -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> cache = new HashMap>(); + + public Image get(TileKey key) { + SoftReference ref = cache.get(key); + if (ref == null) + return null; + return ref.get(); + } + + public void put(TileKey key, Image value) { + cache.put(key, new SoftReference(value)); + } + + public void remove(TileKey key) { + cache.remove(key); + } + } + + private final ITileJobQueue job; + private Map levels = new HashMap(); + private List tileListeners = new ArrayList(); + + 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 null 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 null 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 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 index 00000000..b50a262d --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/tile/TileKey.java @@ -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 index 00000000..35234c01 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/wms/ServiceException.java @@ -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 index 00000000..dc65a535 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/wms/WMSGetMapQuery.java @@ -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 index 00000000..60e02067 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/wms/WMSTileProvider.java @@ -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 index 00000000..eca7bdba --- /dev/null +++ b/org.simantics.district.network.ui.ontology/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.simantics.district.network.ui.ontology/.project b/org.simantics.district.network.ui.ontology/.project new file mode 100644 index 00000000..b6ba8255 --- /dev/null +++ b/org.simantics.district.network.ui.ontology/.project @@ -0,0 +1,34 @@ + + + org.simantics.district.network.ui.ontology + + + + + + org.simantics.graph.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + org.simantics.graph.nature + + 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 index 00000000..1f9cbeac --- /dev/null +++ b/org.simantics.district.network.ui.ontology/META-INF/MANIFEST.MF @@ -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 index 00000000..557c791c --- /dev/null +++ b/org.simantics.district.network.ui.ontology/build.properties @@ -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 index 0000000000000000000000000000000000000000..a8b273c2bbd091c31327bd5e5e903aff3a130594 GIT binary patch literal 2504 zcmai0X;<4u6lL*_Sq*`Nkc1QxC}0AmNfVj{YzLeG=0M>4*q%6QWsQ#{$IxHVf85^t zB!O^FPj$}q=-#)?n^jJ>T-EtDFfcF(45axJKFLv6d#39)bjfrbe~5BUU-{bcEaP}t z@)Upa{K;8O?f8~&X)i+_&oq>YG6hMR`m5!@)i@(`4WC5f%ozVpT*6UpEqx^X>rU{N zsh|YcQT83rR7bh!U-I8ZWojMv68I)FRp&M`ne%+*>o$Udf+aNdUD@6LA3;*wWZWqJ zG#o%B8VM#-W^=OF>lHn#tuQeSuV~yBJW@0b7S6S;8!Yb9(!Go7##V6)`h6NxQ|G>N zn#ygSx~8LD&MD-~Fau}Gmgl=x!#~k}&$xd|=nGgL)0~+ML@Gh7|Zcdvhqp18XYHJy4VQD*5w(?j%>~duvxU##B zsqVyj-4?g8ZiJ>|G??G-BeG+l2V?BD&dqBo~t^z1e}JheTDW;G<4zXmdr{{giOuxjcr=oEqN-r zRLeGX#g+O%o{v>^Bq6C&<@%EIH{o0mPFA>O-v>_TIBE`WgxA9JvN3fNb13^}SJd>L zl+5#ArLY_8Y)m{j+-S8+Hbw}qLt4gSu5ZLOdowdIe-^0_@AY=RM#MPm$Ps-hB!_+B z;qLKdV6TFNL3P1xu^>_g36E4=b9%~D7Gh<*6_@nBF}7B`VbT-^3w?JQmXkNyJsQ*L zvZe5iYUBNx2&4Ob+jK_cM5Wo{d7BoNy2L{3EAYR8-+}Buc+kEI`5UyKfS*GC8trG` zDex;0`M<^cFMXWV@g_V8ZRC+ZuW|An;22=84Ir|=!~4%Mz83Ozw0i)3p97mf4PY+v zD1Ig6t7!KD=HeJ4`%=i4(UyQ`z+)h?-w*jNTHYC)n@2!oe~8p)?4lEV ov5Q@+hzn5elw#k`zo85uc}Anzl|I~9K0pWOMlb8o`yUuCl4qyPW_ literal 0 HcmV?d00001 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 index 00000000..c03c89c5 --- /dev/null +++ b/org.simantics.district.network.ui.ontology/graph/DistrictNetworkUI.pgraph @@ -0,0 +1,31 @@ +L0 = +VP = +VIEW = +SEL = +SEL_UI = +DN = + +DNUI = : 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 index 00000000..d6ae2f61 --- /dev/null +++ b/org.simantics.district.network.ui.ontology/src/org/simantics/district/network/ui/ontology/DistrictNetworkUIResource.java @@ -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() { + 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; + } + +} + -- 2.45.1