Skip to content
Commits on Source (5)
......@@ -10,7 +10,7 @@ JMapViewer
(c) 2010-2011, Michael Vigovsky
(c) 2011-2017, Paul Hartmann
(c) 2011-2016, Gleb Smirnoff
(c) 2011-2019, Vincent Privat
(c) 2011-2020, Vincent Privat
(c) 2011, Jason Huntley
(c) 2012-2016, Simon Legner
(c) 2012, Teemu Koskinen
......
jmapviewer (2.12+dfsg-2) UNRELEASED; urgency=medium
jmapviewer (2.13+dfsg-1) unstable; urgency=medium
* New upstream release.
* Drop Name field from upstream metadata.
* Bump Standards-Version to 4.5.0, no changes.
* Update copyright years for Vincent Privat.
-- Bas Couwenberg <sebastic@debian.org> Mon, 09 Dec 2019 09:10:58 +0100
-- Bas Couwenberg <sebastic@debian.org> Sat, 15 Feb 2020 17:08:39 +0100
jmapviewer (2.12+dfsg-1) unstable; urgency=medium
......
......@@ -25,7 +25,7 @@ Copyright: 2007, Tim Haussmann
2011-2017, Paul Hartmann
2017, Robert Scott
2009-2018, Dirk Stöcker
2011-2019, Vincent Privat
2011-2020, Vincent Privat
License: GPL-2+
Files: src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java
......
......@@ -21,6 +21,7 @@ import javax.imageio.ImageIO;
*/
public final class FeatureAdapter {
private static ApiKeyAdapter apiKeyAdapter = new DefaultApiKeyAdapter();
private static BrowserAdapter browserAdapter = new DefaultBrowserAdapter();
private static ImageAdapter imageAdapter = new DefaultImageAdapter();
private static TranslationAdapter translationAdapter = new DefaultTranslationAdapter();
......@@ -31,20 +32,87 @@ public final class FeatureAdapter {
// private constructor for utility classes
}
/**
* Provider of confidential API keys.
*/
@FunctionalInterface
public interface ApiKeyAdapter {
/**
* Retrieves the API key for the given imagery id.
* @param imageryId imagery id
* @return the API key for the given imagery id
* @throws IOException in case of I/O error
*/
String retrieveApiKey(String imageryId) throws IOException;
}
/**
* Link browser.
*/
@FunctionalInterface
public interface BrowserAdapter {
/**
* Browses to a given link.
* @param url link
*/
void openLink(String url);
}
/**
* Translation support.
*/
public interface TranslationAdapter {
/**
* Translates some text for the current locale.
* <br>
* For example, <code>tr("JMapViewer''s default value is ''{0}''.", val)</code>.
* <br>
* @param text the text to translate.
* Must be a string literal. (No constants or local vars.)
* Can be broken over multiple lines.
* An apostrophe ' must be quoted by another apostrophe.
* @param objects the parameters for the string.
* Mark occurrences in {@code text} with <code>{0}</code>, <code>{1}</code>, ...
* @return the translated string.
*/
String tr(String text, Object... objects);
// TODO: more i18n functions
}
/**
* Logging support.
*/
@FunctionalInterface
public interface LoggingAdapter {
/**
* Retrieves a logger for the given name.
* @param name logger name
* @return logger for the given name
*/
Logger getLogger(String name);
}
/**
* Image provider.
*/
@FunctionalInterface
public interface ImageAdapter {
/**
* Returns a <code>BufferedImage</code> as the result of decoding a supplied <code>URL</code>.
*
* @param input a <code>URL</code> to read from.
* @param readMetadata if {@code true}, makes sure to read image metadata to detect transparency color for non translucent images,
* if any.
* Always considered {@code true} if {@code enforceTransparency} is also {@code true}
* @param enforceTransparency if {@code true}, makes sure to read image metadata and, if the image does not
* provide an alpha channel but defines a {@code TransparentColor} metadata node, that the resulting image
* has a transparency set to {@code TRANSLUCENT} and uses the correct transparent color.
*
* @return a <code>BufferedImage</code> containing the decoded contents of the input, or <code>null</code>.
*
* @throws IllegalArgumentException if <code>input</code> is <code>null</code>.
* @throws IOException if an error occurs during reading.
*/
BufferedImage read(URL input, boolean readMetadata, boolean enforceTransparency) throws IOException;
}
......@@ -70,18 +138,42 @@ public final class FeatureAdapter {
boolean put(String key, String value);
}
/**
* Registers API key adapter.
* @param apiKeyAdapter API key adapter
*/
public static void registerApiKeyAdapter(ApiKeyAdapter apiKeyAdapter) {
FeatureAdapter.apiKeyAdapter = Objects.requireNonNull(apiKeyAdapter);
}
/**
* Registers browser adapter.
* @param browserAdapter browser adapter
*/
public static void registerBrowserAdapter(BrowserAdapter browserAdapter) {
FeatureAdapter.browserAdapter = Objects.requireNonNull(browserAdapter);
}
/**
* Registers image adapter.
* @param imageAdapter image adapter
*/
public static void registerImageAdapter(ImageAdapter imageAdapter) {
FeatureAdapter.imageAdapter = Objects.requireNonNull(imageAdapter);
}
/**
* Registers translation adapter.
* @param translationAdapter translation adapter
*/
public static void registerTranslationAdapter(TranslationAdapter translationAdapter) {
FeatureAdapter.translationAdapter = Objects.requireNonNull(translationAdapter);
}
/**
* Registers logging adapter.
* @param loggingAdapter logging adapter
*/
public static void registerLoggingAdapter(LoggingAdapter loggingAdapter) {
FeatureAdapter.loggingAdapter = Objects.requireNonNull(loggingAdapter);
}
......@@ -95,22 +187,62 @@ public final class FeatureAdapter {
FeatureAdapter.settingsAdapter = Objects.requireNonNull(settingsAdapter);
}
/**
* Retrieves the API key for the given imagery id using the current {@link ApiKeyAdapter}.
* @param imageryId imagery id
* @return the API key for the given imagery id
* @throws IOException in case of I/O error
*/
public static String retrieveApiKey(String imageryId) throws IOException {
return apiKeyAdapter.retrieveApiKey(imageryId);
}
/**
* Opens a link using the current {@link BrowserAdapter}.
* @param url link to open
*/
public static void openLink(String url) {
browserAdapter.openLink(url);
}
/**
* Reads an image using the current {@link ImageAdapter}.
* @param url image URL to read
* @return a <code>BufferedImage</code> containing the decoded contents of the input, or <code>null</code>.
* @throws IOException if an error occurs during reading.
*/
public static BufferedImage readImage(URL url) throws IOException {
return imageAdapter.read(url, false, false);
}
/**
* Translates a text using the current {@link TranslationAdapter}.
* @param text the text to translate.
* Must be a string literal. (No constants or local vars.)
* Can be broken over multiple lines.
* An apostrophe ' must be quoted by another apostrophe.
* @param objects the parameters for the string.
* Mark occurrences in {@code text} with <code>{0}</code>, <code>{1}</code>, ...
* @return the translated string.
*/
public static String tr(String text, Object... objects) {
return translationAdapter.tr(text, objects);
}
/**
* Returns a logger for the given name using the current {@link LoggingAdapter}.
* @param name logger name
* @return logger for the given name
*/
public static Logger getLogger(String name) {
return loggingAdapter.getLogger(name);
}
/**
* Returns a logger for the given class using the current {@link LoggingAdapter}.
* @param klass logger class
* @return logger for the given class
*/
public static Logger getLogger(Class<?> klass) {
return loggingAdapter.getLogger(klass.getSimpleName());
}
......@@ -147,6 +279,19 @@ public final class FeatureAdapter {
return settingsAdapter.put(key, value);
}
/**
* Default API key support that relies on system property named {@code <imageryId>.api-key}.
*/
public static class DefaultApiKeyAdapter implements ApiKeyAdapter {
@Override
public String retrieveApiKey(String imageryId) {
return System.getProperty(imageryId + ".api-key");
}
}
/**
* Default browser support that relies on Java Desktop API.
*/
public static class DefaultBrowserAdapter implements BrowserAdapter {
@Override
public void openLink(String url) {
......@@ -164,6 +309,9 @@ public final class FeatureAdapter {
}
}
/**
* Default image support that relies on Java Image IO API.
*/
public static class DefaultImageAdapter implements ImageAdapter {
@Override
public BufferedImage read(URL input, boolean readMetadata, boolean enforceTransparency) throws IOException {
......@@ -171,6 +319,9 @@ public final class FeatureAdapter {
}
}
/**
* Default "translation" support that do not really translates strings, but only takes care of formatting arguments.
*/
public static class DefaultTranslationAdapter implements TranslationAdapter {
@Override
public String tr(String text, Object... objects) {
......@@ -178,6 +329,9 @@ public final class FeatureAdapter {
}
}
/**
* Default logging support that relies on Java Logging API.
*/
public static class DefaultLoggingAdapter implements LoggingAdapter {
@Override
public Logger getLogger(String name) {
......
......@@ -297,7 +297,14 @@ public class JMapViewer extends JPanel implements TileLoaderListener {
int yMin = Integer.MAX_VALUE;
int xMax = Integer.MIN_VALUE;
int yMax = Integer.MIN_VALUE;
int mapZoomMax = tileController.getTileSource().getMaxZoom();
/*
* Cap mapZoomMax at highest level that prevents overflowing int in X and Y coordinates. As int is from -2^31..2^31.
* Log_2(TileSize) is how many bits are used due to tile size. Math.log(TileSize) / Math.log(2) gives Log_2(TileSize)
* So 31 - tileSizeBits gives maximum zoom that can be handled without overflowing.
* It means 23 for 256 tile size or 22 for 512 tile size
*/
int tileSizeBits = (int) (Math.log(tileController.getTileSource().getDefaultTileSize()) / Math.log(2));
int mapZoomMax = Math.min(31 - tileSizeBits, tileController.getTileSource().getMaxZoom());
if (markers && mapMarkerList != null) {
synchronized (this) {
......
// License: GPL. For details, see Readme.txt file.
package org.openstreetmap.gui.jmapviewer.tilesources;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openstreetmap.gui.jmapviewer.FeatureAdapter;
import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource;
/**
......@@ -24,6 +27,7 @@ import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource;
* {y} - substituted with Y tile number
* {!y} - substituted with Yahoo Y tile number
* {-y} - substituted with reversed Y tile number
* {apiKey} - substituted with API key retrieved for the imagery id
* {switch:VAL_A,VAL_B,VAL_C,...} - substituted with one of VAL_A, VAL_B, VAL_C. Usually
* used to specify many tile servers
* {header:(HEADER_NAME,HEADER_VALUE)} - sets the headers to be sent to tile server
......@@ -45,11 +49,19 @@ public class TemplatedTMSTileSource extends TMSTileSource implements TemplatedTi
private static final Pattern PATTERN_NEG_Y = Pattern.compile("\\{-y\\}");
private static final Pattern PATTERN_SWITCH = Pattern.compile("\\{switch:([^}]+)\\}");
private static final Pattern PATTERN_HEADER = Pattern.compile("\\{header\\(([^,]+),([^}]+)\\)\\}");
private static final Pattern PATTERN_API_KEY = Pattern.compile("\\{apiKey\\}");
private static final Pattern PATTERN_PARAM = Pattern.compile("\\{((?:\\d+-)?z(?:oom)?(:?[+-]\\d+)?|x|y|!y|-y|switch:([^}]+))\\}");
/**
* Pattern used only for compatibility with older JOSM clients. To remove end of 2020, with an update of JOSM wiki
* @deprecated to remove end of 2020
*/
@Deprecated
private static final Pattern PATTERN_API_KEY_COMPATIBILITY = Pattern.compile("_apiKey_");
// CHECKSTYLE.ON: SingleSpaceSeparator
private static final Pattern[] ALL_PATTERNS = {
PATTERN_HEADER, PATTERN_ZOOM, PATTERN_X, PATTERN_Y, PATTERN_Y_YAHOO, PATTERN_NEG_Y, PATTERN_SWITCH
PATTERN_HEADER, PATTERN_ZOOM, PATTERN_X, PATTERN_Y, PATTERN_Y_YAHOO, PATTERN_NEG_Y, PATTERN_SWITCH, PATTERN_API_KEY
};
/**
......@@ -62,25 +74,45 @@ public class TemplatedTMSTileSource extends TMSTileSource implements TemplatedTi
if (cookies != null && !cookies.isEmpty()) {
headers.put(COOKIE_HEADER, cookies);
}
handleTemplate();
handleTemplate(info.getId());
}
private void handleTemplate() {
private void replacePattern(BiConsumer<Matcher, StringBuffer> replaceAction, Pattern... patterns) {
for (Pattern p : patterns) {
StringBuffer output = new StringBuffer();
Matcher m = p.matcher(baseUrl);
while (m.find()) {
replaceAction.accept(m, output);
}
m.appendTail(output);
baseUrl = output.toString();
}
}
private void handleTemplate(String imageryId) {
// Capturing group pattern on switch values
Matcher m = PATTERN_SWITCH.matcher(baseUrl);
if (m.find()) {
rand = new Random();
randomParts = m.group(1).split(",");
}
StringBuffer output = new StringBuffer();
Matcher matcher = PATTERN_HEADER.matcher(baseUrl);
while (matcher.find()) {
// Capturing group pattern on header values
replacePattern((matcher, output) -> {
headers.put(matcher.group(1), matcher.group(2));
matcher.appendReplacement(output, "");
}
matcher.appendTail(output);
baseUrl = output.toString();
m = PATTERN_ZOOM.matcher(this.baseUrl);
}, PATTERN_HEADER);
// Capturing group pattern on API key values
if (imageryId != null) {
replacePattern((matcher, output) -> {
try {
matcher.appendReplacement(output, FeatureAdapter.retrieveApiKey(imageryId));
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}, PATTERN_API_KEY, PATTERN_API_KEY_COMPATIBILITY);
}
// Capturing group pattern on zoom values
m = PATTERN_ZOOM.matcher(baseUrl);
if (m.find()) {
if (m.group(1) != null) {
inverse_zoom = true;
......@@ -93,7 +125,6 @@ public class TemplatedTMSTileSource extends TMSTileSource implements TemplatedTi
zoom_offset += Integer.parseInt(ofs);
}
}
}
@Override
......