diff --git a/LICENSE b/LICENSE index 11c0380de4..d63b1a7204 100644 --- a/LICENSE +++ b/LICENSE @@ -76,7 +76,6 @@ * 3rd party libraries present in the libs folder (https://github.com/osmandapp/Osmand/tree/master/OsmAnd-java/libs): - bzip2-20090327.jar Bzip2 - Apache License - - bsh-core-2.0b4.jar Bean Shell - SPL and LGPL (http://www.beanshell.org/license.html) - commons-logging-1.1.1.jar - Apache License - gnu-trove-osmand.jar GNU trove - LGPL - icu4j-49_1.jar - ICU license (http://source.icu-project.org/repos/icu/icu/trunk/license.html) diff --git a/OsmAnd-java/build.gradle b/OsmAnd-java/build.gradle index 1e72f29fc3..ce0fc99dd1 100644 --- a/OsmAnd-java/build.gradle +++ b/OsmAnd-java/build.gradle @@ -99,7 +99,6 @@ dependencies { implementation group: 'commons-logging', name: 'commons-logging', version: '1.2' implementation group: 'org.json', name: 'json', version: '20171018' implementation 'it.unibo.alice.tuprolog:tuprolog:3.2.1' - implementation 'org.beanshell:bsh-core:2.0b4' implementation 'org.apache.commons:commons-compress:1.17' implementation 'com.moparisthebest:junidecode:0.1.1' implementation 'com.vividsolutions:jts-core:1.14.0' diff --git a/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java b/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java index 08727db2b5..8ff896f9ba 100644 --- a/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java +++ b/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java @@ -15,10 +15,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; -import java.net.URL; import java.net.URLConnection; import java.text.MessageFormat; import java.util.ArrayList; @@ -26,14 +24,13 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import bsh.Interpreter; - public class TileSourceManager { private static final Log log = PlatformUtil.getLog(TileSourceManager.class); - public static final String RULE_BEANSHELL = "beanshell"; public static final String RULE_YANDEX_TRAFFIC = "yandex_traffic"; private static final String RULE_WMS = "wms_tile"; + private static final String RULE_TEMPLATE_1 = "template:1"; + private static final String RND_ALG_WIKIMAPIA = "wikimapia"; private static final TileSourceTemplate MAPNIK_SOURCE = new TileSourceTemplate("OsmAnd (online tiles)", "https://tile.osmand.net/hd/{0}/{1}/{2}.png", ".png", 19, 1, 512, 8, 18000); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ @@ -51,6 +48,9 @@ public class TileSourceManager { MAPILLARY_VECTOR_SOURCE.setHidden(true); } + private static final String PARAM_BING_QUAD_KEY = "{q}"; + private static final String PARAM_RND = "{rnd}"; + public static class TileSourceTemplate implements ITileSource, Cloneable { private int maxZoom; private int minZoom; @@ -63,6 +63,9 @@ public class TileSourceManager { // -1 never expires, private long expirationTimeMillis = -1; private boolean ellipticYTile; + private boolean invertedYTile; + private String randoms; + private String[] randomsArray; private String rule; private boolean hidden; // if hidden in configure map settings, for example mapillary sources @@ -79,15 +82,65 @@ public class TileSourceManager { this.avgSize = avgSize; this.bitDensity = bitDensity; } - - public static String normalizeUrl(String url){ - if(url != null){ - url = url.replaceAll("\\{\\$z\\}", "{0}"); //$NON-NLS-1$ //$NON-NLS-2$ - url = url.replaceAll("\\{\\$x\\}", "{1}"); //$NON-NLS-1$//$NON-NLS-2$ - url = url.replaceAll("\\{\\$y\\}", "{2}"); //$NON-NLS-1$ //$NON-NLS-2$ + + public static String normalizeUrl(String url) { + if (url != null) { + url = url.replaceAll("\\{\\$z\\}", "{0}"); + url = url.replaceAll("\\{\\$x\\}", "{1}"); + url = url.replaceAll("\\{\\$y\\}", "{2}"); + url = url.replaceAll("\\{z\\}", "{0}"); + url = url.replaceAll("\\{x\\}", "{1}"); + url = url.replaceAll("\\{y\\}", "{2}"); } return url; } + + public static String[] buildRandomsArray(String randomsStr) { + List randoms = new ArrayList<>(); + if (!Algorithms.isEmpty(randomsStr)) { + if (randomsStr.equals(RND_ALG_WIKIMAPIA)) { + return new String[]{RND_ALG_WIKIMAPIA}; + } + String[] valuesArray = randomsStr.split(","); + for (String s : valuesArray) { + String[] rangeArray = s.split("-"); + if (rangeArray.length == 2) { + String s1 = rangeArray[0]; + String s2 = rangeArray[1]; + boolean rangeValid = false; + try { + int a = Integer.parseInt(s1); + int b = Integer.parseInt(s2); + if (b > a) { + for (int i = a; i <= b; i++) { + randoms.add(String.valueOf(i)); + } + rangeValid = true; + } + } catch (NumberFormatException e) { + if (s1.length() == 1 && s2.length() == 1) { + char a = s1.charAt(0); + char b = s2.charAt(0); + if (b > a) { + for (char i = a; i <= b; i++) { + randoms.add(String.valueOf(i)); + } + rangeValid = true; + } + } + } + if (!rangeValid) { + randoms.add(s1); + randoms.add(s2); + } + } else { + randoms.add(s); + } + } + } + return randoms.toArray(new String[randoms.size()]); + } + public void setMinZoom(int minZoom) { this.minZoom = minZoom; } @@ -117,6 +170,31 @@ public class TileSourceManager { return ellipticYTile; } + public boolean isInvertedYTile() { + return invertedYTile; + } + + public void setInvertedYTile(boolean invertedYTile) { + this.invertedYTile = invertedYTile; + } + + public String getRandoms() { + return randoms; + } + + public void setRandoms(String randoms) { + this.randoms = randoms; + this.randomsArray = buildRandomsArray(randoms); + } + + public String[] getRandomsArray() { + return randomsArray; + } + + public void setRandomsArray(String[] randomsArray) { + this.randomsArray = randomsArray; + } + @Override public int getBitDensity() { return bitDensity; @@ -208,9 +286,53 @@ public class TileSourceManager { if (urlToLoad == null) { return null; } - return MessageFormat.format(urlToLoad, zoom + "", x + "", y + ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (isInvertedYTile()) { + y = (1 << zoom) - 1 - y; + } + return buildUrlToLoad(urlToLoad, randomsArray, x, y, zoom); } + private static String eqtBingQuadKey(int z, int x, int y) { + char[] NUM_CHAR = {'0', '1', '2', '3'}; + char[] tn = new char[z]; + for (int i = z - 1; i >= 0; i--) { + int num = (x % 2) | ((y % 2) << 1); + tn[i] = NUM_CHAR[num]; + x >>= 1; + y >>= 1; + } + return new String(tn); + } + + public static String buildUrlToLoad(String urlTemplate, String[] randomsArray, int x, int y, int zoom) { + try { + if (randomsArray != null && randomsArray.length > 0) { + String rand; + if (RND_ALG_WIKIMAPIA.equals(randomsArray[0])) { + rand = String.valueOf(x % 4 + (y % 4) * 4); + } else { + rand = randomsArray[(x + y) % randomsArray.length]; + } + urlTemplate = urlTemplate.replace(PARAM_RND, rand); + } else if (urlTemplate.contains(PARAM_RND)) { + log.error("Cannot resolve randoms for template: " + urlTemplate); + return null; + } + + int bingQuadKeyParamIndex = urlTemplate.indexOf(PARAM_BING_QUAD_KEY); + if (bingQuadKeyParamIndex != -1) { + return urlTemplate.replace(PARAM_BING_QUAD_KEY, eqtBingQuadKey(zoom, x, y)); + } + + return MessageFormat.format(urlTemplate, zoom + "", x + "", y + ""); + + } catch (IllegalArgumentException e) { + log.error("Cannot build url for template: " + urlTemplate, e); + return null; + } + } + + public String getUrlTemplate() { return urlToLoad; } @@ -349,6 +471,12 @@ public class TileSourceManager { if (tm.isEllipticYTile()) { properties.put("ellipsoid", tm.isEllipticYTile() + ""); } + if (tm.isInvertedYTile()) { + properties.put("inverted_y", tm.isInvertedYTile() + ""); + } + if (tm.getRandoms() != null) { + properties.put("randoms", tm.getRandoms()); + } if (tm.getExpirationTimeMinutes() != -1) { properties.put("expiration_time_minutes", tm.getExpirationTimeMinutes() + ""); } @@ -390,12 +518,8 @@ public class TileSourceManager { try { if (readUrl.exists()) { BufferedReader reader = new BufferedReader(new InputStreamReader( - new FileInputStream(readUrl), "UTF-8")); //$NON-NLS-1$ + new FileInputStream(readUrl), "UTF-8")); url = reader.readLine(); - // - //url = url.replaceAll("\\{\\$z\\}", "{0}"); //$NON-NLS-1$ //$NON-NLS-2$ - //url = url.replaceAll("\\{\\$x\\}", "{1}"); //$NON-NLS-1$//$NON-NLS-2$ - //url = url.replaceAll("\\{\\$y\\}", "{2}"); //$NON-NLS-1$ //$NON-NLS-2$ url = TileSourceTemplate.normalizeUrl(url); reader.close(); } @@ -457,7 +581,7 @@ public class TileSourceManager { final List templates = new ArrayList(); try { URLConnection connection = NetworkUtils.getHttpURLConnection((https ? "https" : "http") - + "://osmand.net/tile_sources?" + versionAsUrl); + + "://test.osmand.net/tile_sources?" + versionAsUrl); XmlPullParser parser = PlatformUtil.newXMLPullParser(); parser.setInput(connection.getInputStream(), "UTF-8"); int tok; @@ -487,12 +611,12 @@ public class TileSourceManager { } public static TileSourceTemplate createTileSourceTemplate(Map attrs) { - TileSourceTemplate template = null; + TileSourceTemplate template; String rule = attrs.get("rule"); - if(rule == null){ + if (rule == null){ + template = createSimpleTileSourceTemplate(attrs, false); + } else if (RULE_TEMPLATE_1.equalsIgnoreCase(rule)) { template = createSimpleTileSourceTemplate(attrs, false); - } else if(RULE_BEANSHELL.equalsIgnoreCase(rule)){ - template = createBeanshellTileSourceTemplate(attrs); } else if (RULE_WMS.equalsIgnoreCase(rule)) { template = createWmsTileSourceTemplate(attrs); } else if (RULE_YANDEX_TRAFFIC.equalsIgnoreCase(rule)) { @@ -521,8 +645,10 @@ public class TileSourceManager { String ext = attributes.get("ext") == null ? ".jpg" : attributes.get("ext"); int bitDensity = parseInt(attributes, "img_density", 16); int avgTileSize = parseInt(attributes, "avg_img_size", 18000); + String randoms = attributes.get("randoms"); urlTemplate = "http://whoots.mapwarper.net/tms/{0}/{1}/{2}/"+layer+"/"+urlTemplate; TileSourceTemplate templ = new TileSourceTemplate(name, urlTemplate, ext, maxZoom, minZoom, tileSize, bitDensity, avgTileSize); + templ.setRandoms(randoms); return templ; } @@ -534,10 +660,7 @@ public class TileSourceManager { if (name == null || (urlTemplate == null && !ignoreTemplate)) { return null; } - //As I see, here is no changes to urlTemplate - //if(urlTemplate != null){ - //urlTemplate.replace("${x}", "{1}").replace("${y}", "{2}").replace("${z}", "{0}"); - //} + urlTemplate = TileSourceTemplate.normalizeUrl(urlTemplate); int maxZoom = parseInt(attributes, "max_zoom", 18); @@ -551,82 +674,18 @@ public class TileSourceManager { if (Boolean.parseBoolean(attributes.get("ellipsoid"))) { ellipsoid = true; } + boolean invertedY = false; + if (Boolean.parseBoolean(attributes.get("inverted_y"))) { + invertedY = true; + } + String randoms = attributes.get("randoms"); TileSourceTemplate templ = new TileSourceTemplate(name, urlTemplate, ext, maxZoom, minZoom, tileSize, bitDensity, avgTileSize); if(expirationTime >= 0) { templ.setExpirationTimeMinutes(expirationTime); } templ.setEllipticYTile(ellipsoid); + templ.setInvertedYTile(invertedY); + templ.setRandoms(randoms); return templ; } - - private static TileSourceTemplate createBeanshellTileSourceTemplate(Map attributes) { - String name = attributes.get("name"); - String urlTemplate = attributes.get("url_template"); - if (name == null || urlTemplate == null) { - return null; - } - int maxZoom = parseInt(attributes, "max_zoom", 18); - int minZoom = parseInt(attributes, "min_zoom", 5); - int tileSize = parseInt(attributes, "tile_size", 256); - String ext = attributes.get("ext") == null ? ".jpg" : attributes.get("ext"); - int bitDensity = parseInt(attributes, "img_density", 16); - int avgTileSize = parseInt(attributes, "avg_img_size", 18000); - int expirationTime = parseInt(attributes, "expiration_time_minutes", -1); - boolean ellipsoid = false; - if (Boolean.parseBoolean(attributes.get("ellipsoid"))) { - ellipsoid = true; - } - TileSourceTemplate templ; - templ = new BeanShellTileSourceTemplate(name, urlTemplate, ext, maxZoom, minZoom, tileSize, bitDensity, avgTileSize); - templ.setEllipticYTile(ellipsoid); - if(expirationTime > 0) { - templ.setExpirationTimeMinutes(expirationTime); - } - return templ; - } - - public static class BeanShellTileSourceTemplate extends TileSourceTemplate { - - Interpreter bshInterpreter; - - public BeanShellTileSourceTemplate(String name, String urlToLoad, String ext, - int maxZoom, int minZoom, int tileSize, int bitDensity, int avgSize) { - super(name, urlToLoad, ext, maxZoom, minZoom, tileSize, bitDensity, avgSize); - bshInterpreter = new Interpreter(); - try { - bshInterpreter.eval(urlToLoad); - bshInterpreter.getClassManager().setClassLoader(new ClassLoader() { - @Override - public URL getResource(String resName) { - return null; - } - - @Override - public InputStream getResourceAsStream(String resName) { - return null; - } - - @Override - public Class loadClass(String className) throws ClassNotFoundException { - throw new ClassNotFoundException("Error requesting " + className); - } - }); - } catch (bsh.EvalError e) { - log.error("Error executing the map init script " + urlToLoad, e); - } - } - - @Override - public String getUrlToLoad(int x, int y, int zoom) { - try { - return (String) bshInterpreter.eval("getTileUrl("+zoom+","+x+","+y+");"); - } catch (bsh.EvalError e) { - log.error(e.getMessage(), e); - return null; - } - } - - } - - } diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index c290ce321e..6c2b182a15 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -51,7 +51,7 @@ + android:theme="@style/OsmandDarkTheme" android:restoreAnyVersion="true" android:largeHeap="true" android:usesCleartextTraffic="true"> diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index 4d52e76344..707d86fa80 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -459,7 +459,6 @@ dependencies { implementation group: 'commons-logging', name: 'commons-logging', version: '1.2' implementation 'commons-codec:commons-codec:1.11' implementation 'it.unibo.alice.tuprolog:tuprolog:3.2.1' - implementation 'org.beanshell:bsh-core:2.0b4' implementation 'org.apache.commons:commons-compress:1.17' implementation 'com.moparisthebest:junidecode:0.1.1' implementation 'org.immutables:gson:2.5.0' diff --git a/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java b/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java index 89ac033e38..b6ca8f5527 100644 --- a/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java +++ b/OsmAnd/src/net/osmand/plus/SQLiteTileSource.java @@ -3,14 +3,11 @@ package net.osmand.plus; import android.database.sqlite.SQLiteDiskIOException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.os.Build; -import android.widget.Toast; import net.osmand.IndexConstants; import net.osmand.PlatformUtil; import net.osmand.data.QuadRect; import net.osmand.map.ITileSource; -import net.osmand.map.TileSourceManager; import net.osmand.map.TileSourceManager.TileSourceTemplate; import net.osmand.plus.api.SQLiteAPI.SQLiteConnection; import net.osmand.plus.api.SQLiteAPI.SQLiteCursor; @@ -22,13 +19,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; -import java.text.MessageFormat; import java.util.Arrays; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import bsh.Interpreter; public class SQLiteTileSource implements ITileSource { @@ -48,6 +40,9 @@ public class SQLiteTileSource implements ITileSource { private boolean timeSupported = false; private long expirationTimeMillis = -1; // never private boolean isEllipsoid = false; + private boolean invertedY = false; + private String randoms; + private String[] randomsArray; private String rule = null; private String referer = null; @@ -56,9 +51,6 @@ public class SQLiteTileSource implements ITileSource { private OsmandApplication ctx; private boolean onlyReadonlyAvailable = false; - private static final Pattern RND_PATTERN = - Pattern.compile(".*(\\{rnd:([0-9a-zA-Z-_~:\\/?#\\[\\]@!$&'\\(\\)*+,;=]+)\\}).*"); - public SQLiteTileSource(OsmandApplication ctx, File f, List toFindUrl){ this.ctx = ctx; @@ -111,7 +103,6 @@ public class SQLiteTileSource implements ITileSource { return base != null ? base.getTileSize() : tileSize; } - Interpreter bshInterpreter = null; @Override public String getUrlToLoad(int x, int y, int zoom) { if (zoom > maxZoom) @@ -120,47 +111,10 @@ public class SQLiteTileSource implements ITileSource { if (db == null || db.isReadOnly() || urlTemplate == null) { return null; } - - String urlTemplate = this.urlTemplate; - try { - Matcher matcher = RND_PATTERN.matcher(urlTemplate); - if (matcher.matches()) { - int i = 0; - String rndString = matcher.group(1); - String valuesString = matcher.group(2); - if (!Algorithms.isEmpty(valuesString)) { - String[] valuesArray = valuesString.split(","); - if (valuesArray.length > 0) { - i = ((x + y) % valuesArray.length); - } - } - urlTemplate = urlTemplate.replace(rndString, i + ""); - return MessageFormat.format(urlTemplate, zoom + "", x + "", y + ""); - - } else if (TileSourceManager.RULE_BEANSHELL.equalsIgnoreCase(rule)) { - if (Build.VERSION.SDK_INT >= 28) { - Toast.makeText(ctx, "Beanshell is not supported on Android 9+ anymore. Use {rnd:1,2,3,a,b,c} pattern instead.", Toast.LENGTH_LONG).show(); - return null; - } - try { - if (bshInterpreter == null) { - bshInterpreter = new Interpreter(); - bshInterpreter.eval(urlTemplate); - } - return (String) bshInterpreter.eval("getTileUrl(" + zoom + "," + x + "," + y + ");"); - } catch (bsh.EvalError e) { - LOG.debug("getUrlToLoad Error" + e.getMessage()); - Toast.makeText(ctx, e.getMessage(), Toast.LENGTH_LONG).show(); - LOG.error(e.getMessage(), e); - return null; - } - } else { - return MessageFormat.format(urlTemplate, zoom + "", x + "", y + ""); - } - } catch (IllegalArgumentException e) { - LOG.error("Cannot build url for template: " + urlTemplate, e); - return null; + if (invertedY) { + y = (1 << zoom) - 1 - y; } + return TileSourceTemplate.buildUrlToLoad(urlTemplate, randomsArray, x, y, zoom); } @Override @@ -213,7 +167,6 @@ public class SQLiteTileSource implements ITileSource { if(url != -1) { String template = cursor.getString(url); if(!Algorithms.isEmpty(template)){ - //urlTemplate = template; urlTemplate = TileSourceTemplate.normalizeUrl(template); } } @@ -261,6 +214,19 @@ public class SQLiteTileSource implements ITileSource { this.isEllipsoid = true; } } + int invertedY = list.indexOf("inverted_y"); + if(invertedY != -1) { + int set = (int) cursor.getInt(invertedY); + if(set == 1){ + this.invertedY = true; + } + } + int randomsId = list.indexOf("randoms"); + if(randomsId != -1) { + this.randoms = cursor.getString(randomsId); + this.randomsArray = TileSourceTemplate.buildRandomsArray(this.randoms); + + } //boolean inversiveInfoZoom = tnumbering != -1 && "BigPlanet".equals(cursor.getString(tnumbering)); boolean inversiveInfoZoom = inversiveZoom; int mnz = list.indexOf("minzoom"); @@ -500,7 +466,6 @@ public class SQLiteTileSource implements ITileSource { public void closeDB(){ LOG.debug("closeDB"); - bshInterpreter = null; if(timeSupported) { clearOld(); }