Render themes: Android scoped storage, zip render theme, custom resource providers (#804)

This commit is contained in:
Emux 2021-01-21 15:01:19 +02:00 committed by GitHub
parent b9cbd97c40
commit 3bb8ce00c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 778 additions and 119 deletions

View File

@ -2,13 +2,16 @@
## New since 0.15.0
- Android: scoped storage map / theme example [#804](https://github.com/mapsforge/vtm/pull/804)
- Render theme from zip archive [#804](https://github.com/mapsforge/vtm/pull/804)
- Render themes: custom resource providers [#804](https://github.com/mapsforge/vtm/pull/804)
- Nautical unit adapter with feet [#803](https://github.com/mapsforge/vtm/pull/803)
- Many other minor improvements and bug fixes
- [Solved issues](https://github.com/mapsforge/vtm/issues?q=is%3Aclosed+milestone%3A0.16.0)
## Version 0.15.0 (2021-01-01)
- Android: scoped storage example [#785](https://github.com/mapsforge/vtm/pull/785)
- Android: scoped storage map example [#785](https://github.com/mapsforge/vtm/pull/785)
- Mapsforge: map stream support [#784](https://github.com/mapsforge/vtm/pull/784)
- Render theme from Android content providers [#783](https://github.com/mapsforge/vtm/pull/783)
- Render theme xml pull parser [#786](https://github.com/mapsforge/vtm/pull/786)

View File

@ -20,6 +20,9 @@
<item
android:id="@+id/theme_newtron"
android:title="@string/theme_newtron" />
<item
android:id="@+id/theme_external_archive"
android:title="@string/theme_external_archive" />
<item
android:id="@+id/theme_external"
android:title="@string/theme_external" />

View File

@ -6,7 +6,8 @@
<string name="theme_osmagray">Osmagray</string>
<string name="theme_tubes">Tubes</string>
<string name="theme_newtron">NewTron</string>
<string name="theme_external">External theme</string>
<string name="theme_external">External theme (Android 5)</string>
<string name="theme_external_archive">External theme archive</string>
<string name="styler_mode_line">Line</string>
<string name="styler_mode_area">Area</string>
<string name="styler_mode_outline">Outline</string>
@ -15,6 +16,7 @@
<string name="style_2">Hide nature</string>
<string name="menu_gridlayer">Grid</string>
<string name="dialog_reverse_geocoding_title">Reverse Geocoding</string>
<string name="dialog_theme_title">Select a theme</string>
<string name="add">Add</string>
<string name="cancel">Cancel</string>
<string name="error">Error</string>

View File

@ -1,8 +1,9 @@
/*
* Copyright 2014 Hannes Janetzek
* Copyright 2016-2020 devemux86
* Copyright 2016-2021 devemux86
* Copyright 2017 Longri
* Copyright 2018 Gustl22
* Copyright 2021 eddiemuc
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@ -20,13 +21,17 @@
package org.oscim.android.test;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.view.Menu;
import android.view.MenuItem;
import org.oscim.android.theme.ContentRenderTheme;
import org.oscim.android.theme.ContentResolverResourceProvider;
import org.oscim.backend.CanvasAdapter;
import org.oscim.core.MapElement;
import org.oscim.core.MapPosition;
@ -42,9 +47,7 @@ import org.oscim.renderer.BitmapRenderer;
import org.oscim.renderer.GLViewport;
import org.oscim.renderer.bucket.RenderBuckets;
import org.oscim.scalebar.*;
import org.oscim.theme.IRenderTheme;
import org.oscim.theme.ThemeFile;
import org.oscim.theme.VtmThemes;
import org.oscim.theme.*;
import org.oscim.theme.styles.AreaStyle;
import org.oscim.theme.styles.RenderStyle;
import org.oscim.tiling.source.mapfile.MapFileTileSource;
@ -52,15 +55,20 @@ import org.oscim.tiling.source.mapfile.MapInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.zip.ZipInputStream;
public class MapsforgeActivity extends MapActivity {
private static final Logger log = LoggerFactory.getLogger(MapsforgeActivity.class);
static final int SELECT_MAP_FILE = 0;
static final int SELECT_THEME_FILE = 1;
private static final int SELECT_THEME_ARCHIVE = 1;
private static final int SELECT_THEME_DIR = 2;
static final int SELECT_THEME_FILE = 3;
private static final Tag ISSEA_TAG = new Tag("natural", "issea");
private static final Tag NOSEA_TAG = new Tag("natural", "nosea");
@ -71,6 +79,7 @@ public class MapsforgeActivity extends MapActivity {
private final boolean mS3db;
IRenderTheme mTheme;
VectorTileLayer mTileLayer;
private Uri mThemeDirUri;
public MapsforgeActivity() {
this(false);
@ -142,11 +151,19 @@ public class MapsforgeActivity extends MapActivity {
item.setChecked(true);
return true;
case R.id.theme_external:
case R.id.theme_external_archive:
Intent intent = new Intent(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, SELECT_THEME_FILE);
startActivityForResult(intent, SELECT_THEME_ARCHIVE);
return true;
case R.id.theme_external:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
return false;
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, SELECT_THEME_DIR);
return true;
case R.id.gridlayer:
@ -176,75 +193,100 @@ public class MapsforgeActivity extends MapActivity {
return;
}
MapFileTileSource tileSource = new MapFileTileSource();
//tileSource.setPreferredLanguage("en");
try {
Uri uri = data.getData();
MapFileTileSource tileSource = new MapFileTileSource();
//tileSource.setPreferredLanguage("en");
FileInputStream fis = (FileInputStream) getContentResolver().openInputStream(uri);
tileSource.setMapFileInputStream(fis);
mTileLayer = mMap.setBaseMap(tileSource);
loadTheme(null);
if (mS3db)
mMap.layers().add(new S3DBLayer(mMap, mTileLayer));
else
mMap.layers().add(new BuildingLayer(mMap, mTileLayer));
mMap.layers().add(new LabelLayer(mMap, mTileLayer));
DefaultMapScaleBar mapScaleBar = new DefaultMapScaleBar(mMap);
mapScaleBar.setScaleBarMode(DefaultMapScaleBar.ScaleBarMode.BOTH);
mapScaleBar.setDistanceUnitAdapter(MetricUnitAdapter.INSTANCE);
mapScaleBar.setSecondaryDistanceUnitAdapter(ImperialUnitAdapter.INSTANCE);
mapScaleBar.setScaleBarPosition(MapScaleBar.ScaleBarPosition.BOTTOM_LEFT);
MapScaleBarLayer mapScaleBarLayer = new MapScaleBarLayer(mMap, mapScaleBar);
BitmapRenderer renderer = mapScaleBarLayer.getRenderer();
renderer.setPosition(GLViewport.Position.BOTTOM_LEFT);
renderer.setOffset(5 * CanvasAdapter.getScale(), 0);
mMap.layers().add(mapScaleBarLayer);
MapInfo info = tileSource.getMapInfo();
if (!info.boundingBox.contains(mMap.getMapPosition().getGeoPoint())) {
MapPosition pos = new MapPosition();
pos.setByBoundingBox(info.boundingBox, Tile.SIZE * 4, Tile.SIZE * 4);
mMap.setMapPosition(pos);
mPrefs.clear();
}
} catch (Exception e) {
log.error(e.getMessage());
finish();
}
} else if (requestCode == SELECT_THEME_ARCHIVE) {
if (resultCode != Activity.RESULT_OK || data == null)
return;
try {
Uri uri = data.getData();
FileInputStream fis = (FileInputStream) getContentResolver().openInputStream(uri);
tileSource.setMapFileInputStream(fis);
final ZipXmlThemeResourceProvider resourceProvider = new ZipXmlThemeResourceProvider(new ZipInputStream(new BufferedInputStream(getContentResolver().openInputStream(uri))));
final List<String> xmlThemes = resourceProvider.getXmlThemes();
if (xmlThemes.isEmpty())
return;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.dialog_theme_title);
builder.setSingleChoiceItems(xmlThemes.toArray(new String[0]), -1, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
ThemeFile theme = new ZipRenderTheme(xmlThemes.get(which), resourceProvider);
if (mTheme != null)
mTheme.dispose();
mTheme = mMap.setTheme(theme);
mapsforgeTheme(mTheme);
mMenu.findItem(R.id.theme_external_archive).setChecked(true);
}
});
builder.show();
} catch (IOException e) {
log.error(e.getMessage());
finish();
e.printStackTrace();
}
} else if (requestCode == SELECT_THEME_DIR) {
if (resultCode != Activity.RESULT_OK || data == null)
return;
}
mTileLayer = mMap.setBaseMap(tileSource);
loadTheme(null);
mThemeDirUri = data.getData();
if (mS3db)
mMap.layers().add(new S3DBLayer(mMap, mTileLayer));
else
mMap.layers().add(new BuildingLayer(mMap, mTileLayer));
mMap.layers().add(new LabelLayer(mMap, mTileLayer));
DefaultMapScaleBar mapScaleBar = new DefaultMapScaleBar(mMap);
mapScaleBar.setScaleBarMode(DefaultMapScaleBar.ScaleBarMode.BOTH);
mapScaleBar.setDistanceUnitAdapter(MetricUnitAdapter.INSTANCE);
mapScaleBar.setSecondaryDistanceUnitAdapter(ImperialUnitAdapter.INSTANCE);
mapScaleBar.setScaleBarPosition(MapScaleBar.ScaleBarPosition.BOTTOM_LEFT);
MapScaleBarLayer mapScaleBarLayer = new MapScaleBarLayer(mMap, mapScaleBar);
BitmapRenderer renderer = mapScaleBarLayer.getRenderer();
renderer.setPosition(GLViewport.Position.BOTTOM_LEFT);
renderer.setOffset(5 * CanvasAdapter.getScale(), 0);
mMap.layers().add(mapScaleBarLayer);
MapInfo info = tileSource.getMapInfo();
if (!info.boundingBox.contains(mMap.getMapPosition().getGeoPoint())) {
MapPosition pos = new MapPosition();
pos.setByBoundingBox(info.boundingBox, Tile.SIZE * 4, Tile.SIZE * 4);
mMap.setMapPosition(pos);
mPrefs.clear();
}
// Now we have the directory for resources, but we need to let the user also select a theme file
Intent intent = new Intent(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, mThemeDirUri);
startActivityForResult(intent, SELECT_THEME_FILE);
} else if (requestCode == SELECT_THEME_FILE) {
if (resultCode != Activity.RESULT_OK || data == null)
return;
Uri uri = data.getData();
ThemeFile theme = new ContentRenderTheme(getContentResolver(), "", uri);
// Use tessellation with sea and land for Mapsforge themes
if (theme.isMapsforgeTheme()) {
mTileLayer.addHook(new VectorTileLayer.TileLoaderThemeHook() {
@Override
public boolean process(MapTile tile, RenderBuckets buckets, MapElement element, RenderStyle style, int level) {
if (element.tags.contains(ISSEA_TAG) || element.tags.contains(SEA_TAG) || element.tags.contains(NOSEA_TAG)) {
if (style instanceof AreaStyle)
((AreaStyle) style).mesh = true;
}
return false;
}
@Override
public void complete(MapTile tile, boolean success) {
}
});
}
ThemeFile theme = new ContentRenderTheme(getContentResolver(), uri);
theme.setResourceProvider(new ContentResolverResourceProvider(getContentResolver(), mThemeDirUri));
if (mTheme != null)
mTheme.dispose();
mTheme = mMap.setTheme(theme);
mapsforgeTheme(mTheme);
mMenu.findItem(R.id.theme_external).setChecked(true);
}
}
@ -254,4 +296,25 @@ public class MapsforgeActivity extends MapActivity {
mTheme.dispose();
mTheme = mMap.setTheme(VtmThemes.DEFAULT);
}
private void mapsforgeTheme(IRenderTheme theme) {
if (!theme.isMapsforgeTheme())
return;
// Use tessellation with sea and land for Mapsforge themes
mTileLayer.addHook(new VectorTileLayer.TileLoaderThemeHook() {
@Override
public boolean process(MapTile tile, RenderBuckets buckets, MapElement element, RenderStyle style, int level) {
if (element.tags.contains(ISSEA_TAG) || element.tags.contains(SEA_TAG) || element.tags.contains(NOSEA_TAG)) {
if (style instanceof AreaStyle)
((AreaStyle) style).mesh = true;
}
return false;
}
@Override
public void complete(MapTile tile, boolean success) {
}
});
}
}

View File

@ -86,8 +86,8 @@ public class Samples extends Activity {
LinearLayout linearLayout = findViewById(R.id.samples);
linearLayout.addView(createButton(GettingStarted.class));
linearLayout.addView(createLabel(null));
linearLayout.addView(createButton(SimpleMapActivity.class));
linearLayout.addView(createButton(MapsforgeActivity.class));
linearLayout.addView(createButton(SimpleMapActivity.class));
linearLayout.addView(createButton(MBTilesMvtActivity.class));
linearLayout.addView(createButton(MapilionMvtActivity.class));
/*linearLayout.addView(createButton(MapzenMvtActivity.class));

View File

@ -30,6 +30,7 @@ import org.oscim.backend.canvas.Canvas;
import org.oscim.backend.canvas.Paint;
import org.oscim.layers.marker.MarkerSymbol;
import org.oscim.layers.marker.MarkerSymbol.HotspotPlace;
import org.oscim.theme.XmlThemeResourceProvider;
import java.io.IOException;
import java.io.InputStream;
@ -69,8 +70,8 @@ public final class AndroidGraphics extends CanvasAdapter {
}
@Override
public Bitmap loadBitmapAssetImpl(String relativePathPrefix, String src, int width, int height, int percent) throws IOException {
return createBitmap(relativePathPrefix, src, width, height, percent);
public Bitmap loadBitmapAssetImpl(String relativePathPrefix, String src, XmlThemeResourceProvider resourceProvider, int width, int height, int percent) throws IOException {
return createBitmap(relativePathPrefix, src, resourceProvider, width, height, percent);
}
@Override

View File

@ -2,6 +2,7 @@
* Copyright 2010, 2011, 2012 mapsforge.org
* Copyright 2016-2021 devemux86
* Copyright 2017 Andrey Novikov
* Copyright 2021 eddiemuc
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
@ -21,6 +22,7 @@ import android.text.TextUtils;
import org.oscim.theme.IRenderTheme.ThemeException;
import org.oscim.theme.ThemeFile;
import org.oscim.theme.XmlRenderThemeMenuCallback;
import org.oscim.theme.XmlThemeResourceProvider;
import org.oscim.utils.Utils;
import java.io.IOException;
@ -38,6 +40,7 @@ public class AssetsRenderTheme implements ThemeFile {
private boolean mMapsforgeTheme;
private XmlRenderThemeMenuCallback mMenuCallback;
private final String mRelativePathPrefix;
private XmlThemeResourceProvider mResourceProvider;
/**
* @param assetManager the Android asset manager.
@ -99,6 +102,11 @@ public class AssetsRenderTheme implements ThemeFile {
}
}
@Override
public XmlThemeResourceProvider getResourceProvider() {
return mResourceProvider;
}
@Override
public boolean isMapsforgeTheme() {
return mMapsforgeTheme;
@ -113,4 +121,9 @@ public class AssetsRenderTheme implements ThemeFile {
public void setMenuCallback(XmlRenderThemeMenuCallback menuCallback) {
mMenuCallback = menuCallback;
}
@Override
public void setResourceProvider(XmlThemeResourceProvider resourceProvider) {
mResourceProvider = resourceProvider;
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright 2020-2021 devemux86
* Copyright 2021 eddiemuc
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
@ -19,7 +20,7 @@ import android.net.Uri;
import org.oscim.theme.IRenderTheme.ThemeException;
import org.oscim.theme.ThemeFile;
import org.oscim.theme.XmlRenderThemeMenuCallback;
import org.oscim.utils.Utils;
import org.oscim.theme.XmlThemeResourceProvider;
import java.io.IOException;
import java.io.InputStream;
@ -34,29 +35,26 @@ public class ContentRenderTheme implements ThemeFile {
private final ContentResolver mContentResolver;
private boolean mMapsforgeTheme;
private XmlRenderThemeMenuCallback mMenuCallback;
private final String mRelativePathPrefix;
private XmlThemeResourceProvider mResourceProvider;
private final Uri mUri;
/**
* @param contentResolver the Android content resolver.
* @param relativePathPrefix the prefix for all relative resource paths.
* @param uri the XML render theme URI.
* @param contentResolver the Android content resolver.
* @param uri the XML render theme URI.
* @throws ThemeException if an error occurs while reading the render theme XML.
*/
public ContentRenderTheme(ContentResolver contentResolver, String relativePathPrefix, Uri uri) throws ThemeException {
this(contentResolver, relativePathPrefix, uri, null);
public ContentRenderTheme(ContentResolver contentResolver, Uri uri) throws ThemeException {
this(contentResolver, uri, null);
}
/**
* @param contentResolver the Android content resolver.
* @param relativePathPrefix the prefix for all relative resource paths.
* @param uri the XML render theme URI.
* @param menuCallback the interface callback to create a settings menu on the fly.
* @param contentResolver the Android content resolver.
* @param uri the XML render theme URI.
* @param menuCallback the interface callback to create a settings menu on the fly.
* @throws ThemeException if an error occurs while reading the render theme XML.
*/
public ContentRenderTheme(ContentResolver contentResolver, String relativePathPrefix, Uri uri, XmlRenderThemeMenuCallback menuCallback) throws ThemeException {
public ContentRenderTheme(ContentResolver contentResolver, Uri uri, XmlRenderThemeMenuCallback menuCallback) throws ThemeException {
mContentResolver = contentResolver;
mRelativePathPrefix = relativePathPrefix;
mUri = uri;
mMenuCallback = menuCallback;
}
@ -72,9 +70,6 @@ public class ContentRenderTheme implements ThemeFile {
if (getRenderThemeAsStream() != other.getRenderThemeAsStream()) {
return false;
}
if (!Utils.equals(mRelativePathPrefix, other.mRelativePathPrefix)) {
return false;
}
return true;
}
@ -85,7 +80,7 @@ public class ContentRenderTheme implements ThemeFile {
@Override
public String getRelativePathPrefix() {
return mRelativePathPrefix;
return "";
}
@Override
@ -97,6 +92,11 @@ public class ContentRenderTheme implements ThemeFile {
}
}
@Override
public XmlThemeResourceProvider getResourceProvider() {
return mResourceProvider;
}
@Override
public boolean isMapsforgeTheme() {
return mMapsforgeTheme;
@ -111,4 +111,9 @@ public class ContentRenderTheme implements ThemeFile {
public void setMenuCallback(XmlRenderThemeMenuCallback menuCallback) {
mMenuCallback = menuCallback;
}
@Override
public void setResourceProvider(XmlThemeResourceProvider resourceProvider) {
mResourceProvider = resourceProvider;
}
}

View File

@ -0,0 +1,146 @@
/*
* Copyright 2021 eddiemuc
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.android.theme;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import org.oscim.backend.CanvasAdapter;
import org.oscim.theme.XmlThemeResourceProvider;
import org.oscim.utils.IOUtils;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.*;
/**
* An xml theme resource provider resolving resources using Android scoped storage (document framework).
* <p>
* Implementation note: these methods do not use DocumentFile internally,
* but query directly for document info due to vastly better performance.
* Also for better performance, this implementation caches resource uris.
* <p>
* Note: this implementation requires minimum Android 5.0 (API 21)
*/
public class ContentResolverResourceProvider implements XmlThemeResourceProvider {
private final ContentResolver contentResolver;
private final Uri relativeRootUri;
private final Map<String, Uri> resourceUriCache = new HashMap<>();
private static class DocumentInfo {
private final String name;
private final Uri uri;
private final boolean isDirectory;
private DocumentInfo(String name, Uri uri, boolean isDirectory) {
this.name = name;
this.uri = uri;
this.isDirectory = isDirectory;
}
}
public ContentResolverResourceProvider(ContentResolver contentResolver, Uri treeUri) {
this.contentResolver = contentResolver;
this.relativeRootUri = treeUri;
refreshCache();
}
/**
* Build uri cache for one dir level (recursive function).
*/
private void buildCacheLevel(String prefix, Uri dirUri) {
List<DocumentInfo> docs = queryDir(dirUri);
for (DocumentInfo doc : docs) {
if (doc.isDirectory) {
buildCacheLevel(prefix + doc.name + "/", doc.uri);
} else {
resourceUriCache.put(prefix + doc.name, doc.uri);
}
}
}
@Override
public InputStream createInputStream(String relativePath, String source) throws FileNotFoundException {
Uri docUri = resourceUriCache.get(source);
if (docUri != null) {
return contentResolver.openInputStream(docUri);
}
return null;
}
/**
* Query the content of a directory using scoped storage.
*
* @return a list of arrays with info [0: name (String), 1: uri (Uri), 2: isDir (boolean)]
*/
private List<DocumentInfo> queryDir(Uri dirUri) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return Collections.emptyList();
}
if (dirUri == null) {
return Collections.emptyList();
}
List<DocumentInfo> result = new ArrayList<>();
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(dirUri, DocumentsContract.getDocumentId(dirUri));
String[] columns = new String[]{
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE
};
Cursor c = null;
try {
c = contentResolver.query(childrenUri, columns, null, null, null);
while (c.moveToNext()) {
String documentId = c.getString(0);
String name = c.getString(1);
String mimeType = c.getString(2);
Uri uri = DocumentsContract.buildDocumentUriUsingTree(dirUri, documentId);
boolean isDir = DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
result.add(new DocumentInfo(name, uri, isDir));
}
return result;
} finally {
IOUtils.closeQuietly(c);
}
}
/**
* Refresh the uri cache by recreating it.
*/
private void refreshCache() {
resourceUriCache.clear();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
if (relativeRootUri == null) {
return;
}
Uri dirUri = DocumentsContract.buildDocumentUriUsingTree(relativeRootUri, DocumentsContract.getTreeDocumentId(relativeRootUri));
buildCacheLevel(CanvasAdapter.PREFIX_FILE, dirUri);
}
}

View File

@ -23,11 +23,9 @@ import org.oscim.backend.Platform;
import org.oscim.backend.canvas.Bitmap;
import org.oscim.backend.canvas.Canvas;
import org.oscim.backend.canvas.Paint;
import org.oscim.theme.XmlThemeResourceProvider;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
@ -119,7 +117,7 @@ public class AwtGraphics extends CanvasAdapter {
}
@Override
public Bitmap loadBitmapAssetImpl(String relativePathPrefix, String src, int width, int height, int percent) throws IOException {
return createBitmap(relativePathPrefix, src, width, height, percent);
public Bitmap loadBitmapAssetImpl(String relativePathPrefix, String src, XmlThemeResourceProvider resourceProvider, int width, int height, int percent) throws IOException {
return createBitmap(relativePathPrefix, src, resourceProvider, width, height, percent);
}
}

View File

@ -19,7 +19,6 @@
package org.oscim.ios.test;
import com.badlogic.gdx.graphics.glutils.GLVersion;
import org.oscim.backend.GLAdapter;
import org.oscim.backend.canvas.Color;
import org.oscim.core.GeoPoint;
@ -74,7 +73,7 @@ public class IOSPathLayerTest extends GdxMap {
mMap.setMapPosition(0, 0, 1 << 2);
tex = Utils.loadTexture("", "patterns/pike.png", 0, 0, 100);
tex = Utils.loadTexture("", "patterns/pike.png", null, 0, 0, 100);
// tex = new TextureItem(CanvasAdapter.getBitmapAsset("", "patterns/pike.png"));
tex.mipmap = true;

View File

@ -21,6 +21,7 @@ import org.oscim.backend.Platform;
import org.oscim.backend.canvas.Bitmap;
import org.oscim.backend.canvas.Canvas;
import org.oscim.backend.canvas.Paint;
import org.oscim.theme.XmlThemeResourceProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -70,7 +71,7 @@ public class IosGraphics extends CanvasAdapter {
}
@Override
protected Bitmap loadBitmapAssetImpl(String relativePathPrefix, String src, int width, int height, int percent) throws IOException {
return createBitmap(relativePathPrefix, src, width, height, percent);
protected Bitmap loadBitmapAssetImpl(String relativePathPrefix, String src, XmlThemeResourceProvider resourceProvider, int width, int height, int percent) throws IOException {
return createBitmap(relativePathPrefix, src, resourceProvider, width, height, percent);
}
}

View File

@ -13,4 +13,5 @@ dependencies {
sourceSets {
main.java.srcDirs = ['src']
test.java.srcDirs = ['test']
test.resources.srcDirs = ['resources']
}

Binary file not shown.

View File

@ -0,0 +1,86 @@
/*
* Copyright 2021 eddiemuc
* Copyright 2021 devemux86
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.theme;
import org.junit.Assert;
import org.junit.Test;
import org.oscim.utils.IOUtils;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.zip.ZipInputStream;
public class ZipXmlThemeResourceProviderTest {
@Test
public void openZip() throws IOException {
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(ZipXmlThemeResourceProviderTest.class.getResourceAsStream("/xmlthemetest.zip")));
Assert.assertNotNull(zis);
ZipXmlThemeResourceProvider zts = new ZipXmlThemeResourceProvider(zis);
// All files contained
Assert.assertNotNull(zts.createInputStream(null, "file:one.xml"));
Assert.assertNotNull(zts.createInputStream(null, "file:two.xml"));
Assert.assertNotNull(zts.createInputStream(null, "file:res/three.xml"));
Assert.assertNotNull(zts.createInputStream(null, "file:res/blue_star_1.svg"));
Assert.assertNotNull(zts.createInputStream(null, "file:res/test.txt"));
Assert.assertNotNull(zts.createInputStream(null, "file:res/sub/four.xml"));
Assert.assertNotNull(zts.createInputStream(null, "file:res/sub/blue_star_sub_1.svg"));
Assert.assertNotNull(zts.createInputStream(null, "file:res/sub/blue_star_sub_2.svg"));
//Relative Reference ok
Assert.assertNotNull(zts.createInputStream("", "file:res/sub/blue_star_sub_2.svg"));
Assert.assertNotNull(zts.createInputStream("res", "file:sub/blue_star_sub_2.svg"));
Assert.assertNotNull(zts.createInputStream("/", "file:res/sub/blue_star_sub_2.svg"));
Assert.assertNotNull(zts.createInputStream("/res", "file:sub/blue_star_sub_2.svg"));
Assert.assertNotNull(zts.createInputStream("res/", "file:/sub/blue_star_sub_2.svg"));
// Can get same files using various other formats
Assert.assertNotNull(zts.createInputStream(null, "res/sub/blue_star_sub_2.svg"));
Assert.assertNotNull(zts.createInputStream(null, "/res/sub/blue_star_sub_2.svg"));
Assert.assertNotNull(zts.createInputStream(null, "file:/res/sub/blue_star_sub_2.svg"));
// Dirs NOT contained!
Assert.assertNull(zts.createInputStream(null, "file:res/"));
Assert.assertEquals(8, zts.getCount());
List<String> xmlThemes = zts.getXmlThemes();
Assert.assertEquals(4, xmlThemes.size());
Assert.assertTrue(xmlThemes.contains("one.xml"));
Assert.assertTrue(xmlThemes.contains("two.xml"));
Assert.assertTrue(xmlThemes.contains("res/three.xml"));
Assert.assertTrue(xmlThemes.contains("res/sub/four.xml"));
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(zts.createInputStream(null, "file:res/test.txt")));
String line = reader.readLine();
Assert.assertEquals(line, "This is a test");
} finally {
IOUtils.closeQuietly(reader);
}
}
@Test
public void openEmpty() throws IOException {
Assert.assertTrue(new ZipXmlThemeResourceProvider(null).getXmlThemes().isEmpty());
}
}

View File

@ -4,6 +4,7 @@
* Copyright 2016-2021 devemux86
* Copyright 2017 nebular
* Copyright 2017 Andrey Novikov
* Copyright 2021 eddiemuc
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@ -59,6 +60,11 @@ public enum VtmThemes implements ThemeFile {
return AssetAdapter.readFileAsStream(mPath);
}
@Override
public XmlThemeResourceProvider getResourceProvider() {
return null;
}
@Override
public boolean isMapsforgeTheme() {
return false;
@ -71,4 +77,8 @@ public enum VtmThemes implements ThemeFile {
@Override
public void setMenuCallback(XmlRenderThemeMenuCallback menuCallback) {
}
@Override
public void setResourceProvider(XmlThemeResourceProvider resourceProvider) {
}
}

View File

@ -21,11 +21,11 @@ package org.oscim.gdx.client;
import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.TextMetrics;
import org.oscim.backend.CanvasAdapter;
import org.oscim.backend.Platform;
import org.oscim.backend.canvas.Bitmap;
import org.oscim.backend.canvas.Paint;
import org.oscim.theme.XmlThemeResourceProvider;
import java.io.File;
import java.io.InputStream;
@ -68,7 +68,7 @@ public class GwtGdxGraphics extends CanvasAdapter {
}
@Override
public Bitmap loadBitmapAssetImpl(String relativePathPrefix, String src, int width, int height, int percent) {
public Bitmap loadBitmapAssetImpl(String relativePathPrefix, String src, XmlThemeResourceProvider resourceProvider, int width, int height, int percent) {
String pathName = (relativePathPrefix == null || relativePathPrefix.length() == 0 ? "" : relativePathPrefix + File.separatorChar) + src;
return new GwtBitmap(pathName);
}

View File

@ -1,7 +1,8 @@
/*
* Copyright 2013 Hannes Janetzek
* Copyright 2016-2020 devemux86
* Copyright 2016-2021 devemux86
* Copyright 2017 Longri
* Copyright 2021 eddiemuc
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@ -21,6 +22,7 @@ package org.oscim.backend;
import org.oscim.backend.canvas.Bitmap;
import org.oscim.backend.canvas.Canvas;
import org.oscim.backend.canvas.Paint;
import org.oscim.theme.XmlThemeResourceProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,7 +39,7 @@ public abstract class CanvasAdapter {
private static final Logger log = LoggerFactory.getLogger(CanvasAdapter.class);
private static final String PREFIX_ASSETS = "assets:";
private static final String PREFIX_FILE = "file:";
public static final String PREFIX_FILE = "file:";
/**
* The instance provided by backend
@ -156,34 +158,45 @@ public abstract class CanvasAdapter {
* @param src the resource
* @return the bitmap
*/
protected abstract Bitmap loadBitmapAssetImpl(String relativePathPrefix, String src, int width, int height, int percent) throws IOException;
protected abstract Bitmap loadBitmapAssetImpl(String relativePathPrefix, String src, XmlThemeResourceProvider resourceProvider, int width, int height, int percent) throws IOException;
public static Bitmap getBitmapAsset(String relativePathPrefix, String src) throws IOException {
return getBitmapAsset(relativePathPrefix, src, 0, 0, 100);
return getBitmapAsset(relativePathPrefix, src, null, 0, 0, 100);
}
public static Bitmap getBitmapAsset(String relativePathPrefix, String src, int width, int height, int percent) throws IOException {
return g.loadBitmapAssetImpl(relativePathPrefix, src, width, height, percent);
public static Bitmap getBitmapAsset(String relativePathPrefix, String src, XmlThemeResourceProvider resourceProvider, int width, int height, int percent) throws IOException {
return g.loadBitmapAssetImpl(relativePathPrefix, src, resourceProvider, width, height, percent);
}
protected static Bitmap createBitmap(String relativePathPrefix, String src, int width, int height, int percent) throws IOException {
protected static Bitmap createBitmap(String relativePathPrefix, String src, XmlThemeResourceProvider resourceProvider, int width, int height, int percent) throws IOException {
if (src == null || src.length() == 0) {
// no image source defined
return null;
}
InputStream inputStream;
if (src.startsWith(PREFIX_ASSETS)) {
src = src.substring(PREFIX_ASSETS.length());
inputStream = inputStreamFromAssets(relativePathPrefix, src);
} else if (src.startsWith(PREFIX_FILE)) {
src = src.substring(PREFIX_FILE.length());
inputStream = inputStreamFromFile(relativePathPrefix, src);
} else {
inputStream = inputStreamFromFile(relativePathPrefix, src);
InputStream inputStream = null;
if (resourceProvider != null) {
try {
inputStream = resourceProvider.createInputStream(relativePathPrefix, src);
} catch (IOException ioe) {
log.debug("Exception trying to access resource: " + src + " using custom provider: " + ioe);
// Ignore and try to resolve input stream using the standard process
}
}
if (inputStream == null)
if (inputStream == null) {
if (src.startsWith(PREFIX_ASSETS)) {
src = src.substring(PREFIX_ASSETS.length());
inputStream = inputStreamFromAssets(relativePathPrefix, src);
} else if (src.startsWith(PREFIX_FILE)) {
src = src.substring(PREFIX_FILE.length());
inputStream = inputStreamFromFile(relativePathPrefix, src);
} else {
inputStream = inputStreamFromFile(relativePathPrefix, src);
if (inputStream == null)
inputStream = inputStreamFromAssets(relativePathPrefix, src);
}
}
// Fallback to internal resources

View File

@ -3,6 +3,7 @@
* Copyright 2013 Hannes Janetzek
* Copyright 2016-2021 devemux86
* Copyright 2017 Andrey Novikov
* Copyright 2021 eddiemuc
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@ -37,6 +38,7 @@ public class ExternalRenderTheme implements ThemeFile {
private boolean mMapsforgeTheme;
private XmlRenderThemeMenuCallback mMenuCallback;
private final String mPath;
private XmlThemeResourceProvider mResourceProvider;
/**
* @param fileName the path to the XML render theme file.
@ -109,6 +111,11 @@ public class ExternalRenderTheme implements ThemeFile {
return is;
}
@Override
public XmlThemeResourceProvider getResourceProvider() {
return mResourceProvider;
}
@Override
public boolean isMapsforgeTheme() {
return mMapsforgeTheme;
@ -123,4 +130,9 @@ public class ExternalRenderTheme implements ThemeFile {
public void setMenuCallback(XmlRenderThemeMenuCallback menuCallback) {
mMenuCallback = menuCallback;
}
@Override
public void setResourceProvider(XmlThemeResourceProvider resourceProvider) {
mResourceProvider = resourceProvider;
}
}

View File

@ -1,6 +1,7 @@
/*
* Copyright 2016-2021 devemux86
* Copyright 2017 Andrey Novikov
* Copyright 2021 eddiemuc
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
@ -31,12 +32,14 @@ public class StreamRenderTheme implements ThemeFile {
private boolean mMapsforgeTheme;
private XmlRenderThemeMenuCallback mMenuCallback;
private final String mRelativePathPrefix;
private XmlThemeResourceProvider mResourceProvider;
/**
* @param relativePathPrefix the prefix for all relative resource paths.
* @param inputStream an input stream containing valid render theme XML data.
* @throws ThemeException if an error occurs while reading the render theme XML.
*/
public StreamRenderTheme(String relativePathPrefix, InputStream inputStream) {
public StreamRenderTheme(String relativePathPrefix, InputStream inputStream) throws ThemeException {
this(relativePathPrefix, inputStream, null);
}
@ -44,8 +47,9 @@ public class StreamRenderTheme implements ThemeFile {
* @param relativePathPrefix the prefix for all relative resource paths.
* @param inputStream an input stream containing valid render theme XML data.
* @param menuCallback the interface callback to create a settings menu on the fly.
* @throws ThemeException if an error occurs while reading the render theme XML.
*/
public StreamRenderTheme(String relativePathPrefix, InputStream inputStream, XmlRenderThemeMenuCallback menuCallback) {
public StreamRenderTheme(String relativePathPrefix, InputStream inputStream, XmlRenderThemeMenuCallback menuCallback) throws ThemeException {
mRelativePathPrefix = relativePathPrefix;
mInputStream = inputStream;
mMenuCallback = menuCallback;
@ -83,6 +87,11 @@ public class StreamRenderTheme implements ThemeFile {
return mInputStream;
}
@Override
public XmlThemeResourceProvider getResourceProvider() {
return mResourceProvider;
}
@Override
public boolean isMapsforgeTheme() {
return mMapsforgeTheme;
@ -97,4 +106,9 @@ public class StreamRenderTheme implements ThemeFile {
public void setMenuCallback(XmlRenderThemeMenuCallback menuCallback) {
mMenuCallback = menuCallback;
}
@Override
public void setResourceProvider(XmlThemeResourceProvider resourceProvider) {
mResourceProvider = resourceProvider;
}
}

View File

@ -3,6 +3,7 @@
* Copyright 2013 Hannes Janetzek
* Copyright 2016-2021 devemux86
* Copyright 2017 Andrey Novikov
* Copyright 2021 eddiemuc
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@ -44,6 +45,11 @@ public interface ThemeFile extends Serializable {
*/
InputStream getRenderThemeAsStream() throws ThemeException;
/**
* @return a custom provider to retrieve resources internally referenced by "src" attribute (e.g. images, icons).
*/
XmlThemeResourceProvider getResourceProvider();
/**
* Tells ThemeLoader if theme file is in Mapsforge format
*
@ -60,4 +66,9 @@ public interface ThemeFile extends Serializable {
* @param menuCallback the interface callback to create a settings menu on the fly.
*/
void setMenuCallback(XmlRenderThemeMenuCallback menuCallback);
/**
* @param resourceProvider a custom provider to retrieve resources internally referenced by "src" attribute (e.g. images, icons).
*/
void setResourceProvider(XmlThemeResourceProvider resourceProvider);
}

View File

@ -8,6 +8,7 @@
* Copyright 2018-2019 Gustl22
* Copyright 2018 Izumi Kawashima
* Copyright 2019 Murray Hughes
* Copyright 2021 eddiemuc
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@ -664,7 +665,7 @@ public class XmlThemeBuilder {
} else {
if (src != null) {
float symbolScale = Parameters.SYMBOL_SCALING == Parameters.SymbolScaling.ALL ? CanvasAdapter.symbolScale : 1;
b.texture = Utils.loadTexture(mTheme.getRelativePathPrefix(), src, b.symbolWidth, b.symbolHeight, (int) (b.symbolPercent * symbolScale));
b.texture = Utils.loadTexture(mTheme.getRelativePathPrefix(), src, mTheme.getResourceProvider(), b.symbolWidth, b.symbolHeight, (int) (b.symbolPercent * symbolScale));
}
if (b.texture != null && hasSymbol) {
@ -777,7 +778,7 @@ public class XmlThemeBuilder {
}
if (src != null)
b.texture = Utils.loadTexture(mTheme.getRelativePathPrefix(), src, b.symbolWidth, b.symbolHeight, b.symbolPercent);
b.texture = Utils.loadTexture(mTheme.getRelativePathPrefix(), src, mTheme.getResourceProvider(), b.symbolWidth, b.symbolHeight, b.symbolPercent);
return b.build();
}
@ -1103,7 +1104,7 @@ public class XmlThemeBuilder {
String lowValue = symbol.toLowerCase(Locale.ENGLISH);
if (lowValue.endsWith(".png") || lowValue.endsWith(".svg")) {
try {
b.bitmap = CanvasAdapter.getBitmapAsset(mTheme.getRelativePathPrefix(), symbol, b.symbolWidth, b.symbolHeight, (int) (b.symbolPercent * CanvasAdapter.symbolScale));
b.bitmap = CanvasAdapter.getBitmapAsset(mTheme.getRelativePathPrefix(), symbol, mTheme.getResourceProvider(), b.symbolWidth, b.symbolHeight, (int) (b.symbolPercent * CanvasAdapter.symbolScale));
} catch (Exception e) {
log.error("{}: {}", symbol, e.getMessage());
}
@ -1257,7 +1258,7 @@ public class XmlThemeBuilder {
symbolScale = CanvasAdapter.symbolScale;
break;
}
Bitmap bitmap = CanvasAdapter.getBitmapAsset(mTheme.getRelativePathPrefix(), b.src, b.symbolWidth, b.symbolHeight, (int) (b.symbolPercent * symbolScale));
Bitmap bitmap = CanvasAdapter.getBitmapAsset(mTheme.getRelativePathPrefix(), b.src, mTheme.getResourceProvider(), b.symbolWidth, b.symbolHeight, (int) (b.symbolPercent * symbolScale));
if (bitmap != null)
return buildSymbol(b, b.src, bitmap);
} catch (Exception e) {

View File

@ -0,0 +1,32 @@
/*
* Copyright 2021 eddiemuc
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.theme;
import java.io.IOException;
import java.io.InputStream;
/**
* Interface for a provider of resources referenced inside XML themes.
*/
public interface XmlThemeResourceProvider {
/**
* @param relativePath a relative path to use as a base for search in the resource provuider
* @param source a source string parsed out of an XML render theme "src" attribute.
* @return an InputStream to read the resource data from.
* @throws IOException if the resource cannot be found or an access error occurred.
*/
InputStream createInputStream(String relativePath, String source) throws IOException;
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2021 devemux86
* Copyright 2021 eddiemuc
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.theme;
import org.oscim.theme.IRenderTheme.ThemeException;
import org.oscim.utils.Utils;
import java.io.IOException;
import java.io.InputStream;
/**
* A ZipRenderTheme allows for customizing the rendering style of the map
* via an XML from an archive.
*/
public class ZipRenderTheme implements ThemeFile {
private static final long serialVersionUID = 1L;
private boolean mMapsforgeTheme;
private XmlRenderThemeMenuCallback mMenuCallback;
private final String mRelativePathPrefix;
private XmlThemeResourceProvider mResourceProvider;
protected final String mXmlTheme;
/**
* @param xmlTheme the XML theme path in the archive.
* @param resourceProvider the custom provider to retrieve resources internally referenced by "src" attribute (e.g. images, icons).
* @throws ThemeException if an error occurs while reading the render theme XML.
*/
public ZipRenderTheme(String xmlTheme, XmlThemeResourceProvider resourceProvider) throws ThemeException {
this(xmlTheme, resourceProvider, null);
}
/**
* @param xmlTheme the XML theme path in the archive.
* @param resourceProvider the custom provider to retrieve resources internally referenced by "src" attribute (e.g. images, icons).
* @param menuCallback the interface callback to create a settings menu on the fly.
* @throws ThemeException if an error occurs while reading the render theme XML.
*/
public ZipRenderTheme(String xmlTheme, XmlThemeResourceProvider resourceProvider, XmlRenderThemeMenuCallback menuCallback) throws ThemeException {
mXmlTheme = xmlTheme;
mResourceProvider = resourceProvider;
mMenuCallback = menuCallback;
mRelativePathPrefix = xmlTheme.substring(0, xmlTheme.lastIndexOf("/") + 1);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof ZipRenderTheme)) {
return false;
}
ZipRenderTheme other = (ZipRenderTheme) obj;
if (getRenderThemeAsStream() != other.getRenderThemeAsStream()) {
return false;
}
if (!Utils.equals(mRelativePathPrefix, other.mRelativePathPrefix)) {
return false;
}
return true;
}
@Override
public XmlRenderThemeMenuCallback getMenuCallback() {
return mMenuCallback;
}
@Override
public String getRelativePathPrefix() {
return mRelativePathPrefix;
}
@Override
public InputStream getRenderThemeAsStream() throws ThemeException {
try {
return mResourceProvider.createInputStream(mRelativePathPrefix, mXmlTheme.substring(mXmlTheme.lastIndexOf("/") + 1));
} catch (IOException e) {
throw new ThemeException(e.getMessage());
}
}
@Override
public XmlThemeResourceProvider getResourceProvider() {
return mResourceProvider;
}
@Override
public boolean isMapsforgeTheme() {
return mMapsforgeTheme;
}
@Override
public void setMapsforgeTheme(boolean mapsforgeTheme) {
mMapsforgeTheme = mapsforgeTheme;
}
@Override
public void setMenuCallback(XmlRenderThemeMenuCallback menuCallback) {
mMenuCallback = menuCallback;
}
@Override
public void setResourceProvider(XmlThemeResourceProvider resourceProvider) {
mResourceProvider = resourceProvider;
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 2021 eddiemuc
* Copyright 2021 devemux86
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.theme;
import org.oscim.backend.CanvasAdapter;
import org.oscim.utils.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Resource provider reading resource files out of a zip input stream.
* <p>
* Resources are cached.
*/
public class ZipXmlThemeResourceProvider implements XmlThemeResourceProvider {
private final Map<String, byte[]> files = new HashMap<>();
private final List<String> xmlThemes = new ArrayList<>();
/**
* @param zipInputStream zip stream to read resources from
* @throws IOException if a problem occurs reading the stream
*/
public ZipXmlThemeResourceProvider(ZipInputStream zipInputStream) throws IOException {
this(zipInputStream, Integer.MAX_VALUE);
}
/**
* @param zipInputStream zip stream to read resources from
* @param maxResourceSizeToCache only resources in the zip stream with a maximum size of this parameter (in bytes) are cached and provided
* @throws IOException if a problem occurs reading the stream
*/
public ZipXmlThemeResourceProvider(ZipInputStream zipInputStream, int maxResourceSizeToCache) throws IOException {
if (zipInputStream == null) {
return;
}
try {
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (zipEntry.isDirectory() || zipEntry.getSize() > maxResourceSizeToCache) {
continue;
}
byte[] entry = streamToBytes(zipInputStream, (int) zipEntry.getSize());
String fileName = zipEntry.getName();
if (fileName.startsWith("/")) {
fileName = fileName.substring(1);
}
files.put(fileName, entry);
if (fileName.toLowerCase(Locale.ROOT).endsWith(".xml")) {
xmlThemes.add(fileName);
}
}
} finally {
IOUtils.closeQuietly(zipInputStream);
}
}
@Override
public InputStream createInputStream(String relativePath, String source) {
String sourceKey = source;
if (sourceKey.startsWith(CanvasAdapter.PREFIX_FILE)) {
sourceKey = sourceKey.substring(CanvasAdapter.PREFIX_FILE.length());
}
if (sourceKey.startsWith("/")) {
sourceKey = sourceKey.substring(1);
}
if (relativePath != null) {
if (relativePath.startsWith("/")) {
relativePath = relativePath.substring(1);
}
if (relativePath.endsWith("/")) {
relativePath = relativePath.substring(0, relativePath.length() - 1);
}
sourceKey = relativePath.isEmpty() ? sourceKey : relativePath + "/" + sourceKey;
}
if (files.containsKey(sourceKey)) {
return new ByteArrayInputStream(files.get(sourceKey));
}
return null;
}
/**
* @return the number of files in the archive.
*/
public int getCount() {
return files.size();
}
/**
* @return the XML theme paths in the archive.
*/
public List<String> getXmlThemes() {
return xmlThemes;
}
private static byte[] streamToBytes(InputStream in, int size) throws IOException {
byte[] bytes = new byte[size];
int count, offset = 0;
while ((count = in.read(bytes, offset, size)) > 0) {
size -= count;
offset += count;
}
return bytes;
}
}

View File

@ -19,6 +19,7 @@ import org.oscim.backend.CanvasAdapter;
import org.oscim.backend.canvas.Bitmap;
import org.oscim.backend.canvas.Canvas;
import org.oscim.renderer.bucket.TextureItem;
import org.oscim.theme.XmlThemeResourceProvider;
import org.oscim.utils.math.MathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,12 +38,12 @@ public final class Utils {
/**
* Load a texture from a specified location and optional dimensions.
*/
public static TextureItem loadTexture(String relativePathPrefix, String src, int width, int height, int percent) {
public static TextureItem loadTexture(String relativePathPrefix, String src, XmlThemeResourceProvider resourceProvider, int width, int height, int percent) {
if (src == null || src.length() == 0)
return null;
try {
Bitmap bitmap = CanvasAdapter.getBitmapAsset(relativePathPrefix, src, width, height, percent);
Bitmap bitmap = CanvasAdapter.getBitmapAsset(relativePathPrefix, src, resourceProvider, width, height, percent);
if (bitmap != null) {
log.debug("loading {}", src);
return new TextureItem(potBitmap(bitmap), true);