package net.osmand.core.android; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import net.osmand.core.jni.IMapTiledSymbolsProvider; import net.osmand.core.jni.IObfsCollection; import net.osmand.core.jni.IRasterMapLayerProvider; import net.osmand.core.jni.MapObjectsSymbolsProvider; import net.osmand.core.jni.MapPresentationEnvironment; import net.osmand.core.jni.MapPresentationEnvironment.LanguagePreference; import net.osmand.core.jni.MapPrimitivesProvider; import net.osmand.core.jni.MapPrimitiviser; import net.osmand.core.jni.MapRasterLayerProvider_Software; import net.osmand.core.jni.MapRendererSetupOptions; import net.osmand.core.jni.MapStylesCollection; import net.osmand.core.jni.ObfMapObjectsProvider; import net.osmand.core.jni.QStringStringHash; import net.osmand.core.jni.ResolvedMapStyle; import net.osmand.core.jni.SwigUtilities; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandSettings; import net.osmand.plus.OsmandSettings.CommonPreference; import net.osmand.plus.render.RendererRegistry; import net.osmand.render.RenderingRuleProperty; import net.osmand.render.RenderingRuleStorageProperties; import net.osmand.render.RenderingRulesStorage; import net.osmand.util.Algorithms; /** * Context container and utility class for MapRendererView and derivatives. * @author Alexey Pelykh * */ public class MapRendererContext implements RendererRegistry.IRendererLoadedEventListener { private static final String TAG = "MapRendererContext"; private static final int OBF_RASTER_LAYER = 0; private OsmandApplication app; // input parameters private MapStylesCollection mapStylesCollection; private IObfsCollection obfsCollection; private boolean nightMode; private final float density; // ached objects private Map<String, ResolvedMapStyle> mapStyles = new HashMap<String, ResolvedMapStyle>(); private CachedMapPresentation presentationObjectParams; private MapPresentationEnvironment mapPresentationEnvironment; private IMapTiledSymbolsProvider obfMapSymbolsProvider; private IRasterMapLayerProvider obfMapRasterLayerProvider; private MapRendererView mapRendererView; private float cachedReferenceTileSize; public MapRendererContext(OsmandApplication app, float density) { this.app = app; this.density = density; } /** * Bounds specified map renderer view to this context * @param mapRendererView Reference to MapRendererView */ public void setMapRendererView(MapRendererView mapRendererView) { boolean update = (this.mapRendererView != mapRendererView); this.mapRendererView = mapRendererView; if (!update) { return; } if (mapRendererView != null) { applyCurrentContextToView(); } } public void setNightMode(boolean nightMode) { if (nightMode != this.nightMode) { this.nightMode = nightMode; updateMapSettings(); } } public void updateMapSettings() { if (mapRendererView instanceof AtlasMapRendererView && cachedReferenceTileSize != getReferenceTileSize()) { ((AtlasMapRendererView) mapRendererView).setReferenceTileSizeOnScreenInPixels(getReferenceTileSize()); } if(mapPresentationEnvironment != null) { updateMapPresentationEnvironment(); } } /** * Setup OBF map on layer 0 with symbols * @param obfsCollection OBFs collection */ public void setupObfMap(MapStylesCollection mapStylesCollection, IObfsCollection obfsCollection) { this.obfsCollection = obfsCollection; this.mapStylesCollection = mapStylesCollection; updateMapPresentationEnvironment(); recreateRasterAndSymbolsProvider(); } protected int getRasterTileSize() { return (int)(getReferenceTileSize() * app.getSettings().MAP_DENSITY.get()); } private float getReferenceTileSize() { return 256 * Math.max(1, density); } /** * Update map presentation environment and everything that depends on it */ private void updateMapPresentationEnvironment() { // Create new map presentation environment String langId = app.getSettings().MAP_PREFERRED_LOCALE.get(); // TODO make setting LanguagePreference langPref = LanguagePreference.LocalizedOrNative; String rendName = app.getSettings().RENDERER.get(); if (rendName.length() == 0 || rendName.equals(RendererRegistry.DEFAULT_RENDER)) { rendName = "default"; } if (!mapStyles.containsKey(rendName)) { Log.d(TAG, "Style '" + rendName + "' not in cache"); if (mapStylesCollection.getStyleByName(rendName) == null) { Log.d(TAG, "Unknown '" + rendName + "' style, need to load"); // Ensure parents are loaded (this may also trigger load) app.getRendererRegistry().getRenderer(rendName); if (mapStylesCollection.getStyleByName(rendName) == null) { try { loadStyleFromStream(rendName, app.getRendererRegistry().getInputStream(rendName)); } catch (IOException e) { Log.e(TAG, "Failed to load '" + rendName + "'", e); } } } ResolvedMapStyle mapStyle = mapStylesCollection.getResolvedStyleByName(rendName); if (mapStyle != null) { mapStyles.put(rendName, mapStyle); } else { Log.d(TAG, "Failed to resolve '" + rendName + "', will use 'default'"); rendName = "default"; } } ResolvedMapStyle mapStyle = mapStyles.get(rendName); CachedMapPresentation pres = new CachedMapPresentation(langId, langPref, mapStyle, density, app.getSettings().MAP_DENSITY.get(), app.getSettings().TEXT_SCALE.get()); if (this.presentationObjectParams == null || !this.presentationObjectParams.equalsFields(pres)) { this.presentationObjectParams = pres; mapPresentationEnvironment = new MapPresentationEnvironment(mapStyle, density, app.getSettings().MAP_DENSITY.get(), app.getSettings().TEXT_SCALE.get(), langId, langPref); } QStringStringHash convertedStyleSettings = getMapStyleSettings(); mapPresentationEnvironment.setSettings(convertedStyleSettings); if (obfMapRasterLayerProvider != null || obfMapSymbolsProvider != null) { recreateRasterAndSymbolsProvider(); } } protected QStringStringHash getMapStyleSettings() { // Apply map style settings OsmandSettings prefs = app.getSettings(); RenderingRulesStorage storage = app.getRendererRegistry().getCurrentSelectedRenderer(); Map<String, String> props = new HashMap<String, String>(); for (RenderingRuleProperty customProp : storage.PROPS.getCustomRules()) { if(RenderingRuleStorageProperties.UI_CATEGORY_HIDDEN.equals(customProp.getCategory())){ continue; } else if (customProp.isBoolean()) { CommonPreference<Boolean> pref = prefs.getCustomRenderBooleanProperty(customProp.getAttrName()); props.put(customProp.getAttrName(), pref.get() + ""); } else { CommonPreference<String> settings = prefs.getCustomRenderProperty(customProp.getAttrName()); String res = settings.get(); if (!Algorithms.isEmpty(res)) { props.put(customProp.getAttrName(), res); } } } QStringStringHash convertedStyleSettings = new QStringStringHash(); for (Map.Entry<String, String> setting : props.entrySet()) { convertedStyleSettings.set(setting.getKey(), setting.getValue()); } if (nightMode) { convertedStyleSettings.set("nightMode", "true"); } return convertedStyleSettings; } private void recreateRasterAndSymbolsProvider() { // Create new map primitiviser // TODO Victor ask MapPrimitiviser, ObfMapObjectsProvider MapPrimitiviser mapPrimitiviser = new MapPrimitiviser(mapPresentationEnvironment); ObfMapObjectsProvider obfMapObjectsProvider = new ObfMapObjectsProvider(obfsCollection); // Create new map primitives provider MapPrimitivesProvider mapPrimitivesProvider = new MapPrimitivesProvider(obfMapObjectsProvider, mapPrimitiviser, getRasterTileSize()); updateObfMapRasterLayerProvider(mapPrimitivesProvider); updateObfMapSymbolsProvider(mapPrimitivesProvider); } private void updateObfMapRasterLayerProvider(MapPrimitivesProvider mapPrimitivesProvider) { // Create new OBF map raster layer provider obfMapRasterLayerProvider = new MapRasterLayerProvider_Software(mapPrimitivesProvider); // In case there's bound view and configured layer, perform setup if (mapRendererView != null) { mapRendererView.setMapLayerProvider(OBF_RASTER_LAYER, obfMapRasterLayerProvider); } } private void updateObfMapSymbolsProvider(MapPrimitivesProvider mapPrimitivesProvider) { // If there's current provider and bound view, remove it if (obfMapSymbolsProvider != null && mapRendererView != null) { mapRendererView.removeSymbolsProvider(obfMapSymbolsProvider); } // Create new OBF map symbols provider obfMapSymbolsProvider = new MapObjectsSymbolsProvider(mapPrimitivesProvider, getReferenceTileSize()); // If there's bound view, add new provider if (mapRendererView != null) { mapRendererView.addSymbolsProvider(obfMapSymbolsProvider); } } private void applyCurrentContextToView() { mapRendererView.setMapRendererSetupOptionsConfigurator( new MapRendererView.IMapRendererSetupOptionsConfigurator() { @Override public void configureMapRendererSetupOptions( MapRendererSetupOptions mapRendererSetupOptions) { mapRendererSetupOptions.setMaxNumberOfRasterMapLayersInBatch(1); } }); if (mapRendererView instanceof AtlasMapRendererView) { cachedReferenceTileSize = getReferenceTileSize(); ((AtlasMapRendererView)mapRendererView).setReferenceTileSizeOnScreenInPixels(cachedReferenceTileSize); } // Layers if (obfMapRasterLayerProvider != null) { mapRendererView.setMapLayerProvider(OBF_RASTER_LAYER, obfMapRasterLayerProvider); } // Symbols if (obfMapSymbolsProvider != null) { mapRendererView.addSymbolsProvider(obfMapSymbolsProvider); } } private class CachedMapPresentation { String langId ; LanguagePreference langPref; ResolvedMapStyle mapStyle; float displayDensityFactor; float mapScaleFactor; float symbolsScaleFactor; public CachedMapPresentation(String langId, LanguagePreference langPref, ResolvedMapStyle mapStyle, float displayDensityFactor, float mapScaleFactor, float symbolsScaleFactor) { this.langId = langId; this.langPref = langPref; this.mapStyle = mapStyle; this.displayDensityFactor = displayDensityFactor; this.mapScaleFactor = mapScaleFactor; this.symbolsScaleFactor = symbolsScaleFactor; } public boolean equalsFields(CachedMapPresentation other ) { if (Double.compare(displayDensityFactor, other.displayDensityFactor) != 0) return false; if (Double.compare(mapScaleFactor, other.mapScaleFactor) != 0) return false; if (Double.compare(symbolsScaleFactor, other.symbolsScaleFactor) != 0) return false; if (langId == null) { if (other.langId != null) return false; } else if (!langId.equals(other.langId)) return false; if (langPref != other.langPref) return false; if (mapStyle == null) { if (other.mapStyle != null) return false; } else if (!mapStyle.equals(other.mapStyle)) return false; return true; } } public void onRendererLoaded(String name, RenderingRulesStorage rules, InputStream source) { loadStyleFromStream(name, source); } private void loadStyleFromStream(String name, InputStream source) { if(source == null) { return; } if (RendererRegistry.DEFAULT_RENDER.equals(name)) { if (source != null) { try { source.close(); } catch(IOException e) {} } return; } Log.d(TAG, "Going to pass '" + name + "' style content to native"); byte[] content; try { ByteArrayOutputStream intermediateBuffer = new ByteArrayOutputStream(); int nRead; byte[] data = new byte[16384]; while ((nRead = source.read(data, 0, data.length)) != -1) { intermediateBuffer.write(data, 0, nRead); } intermediateBuffer.flush(); content = intermediateBuffer.toByteArray(); } catch(IOException e) { Log.e(TAG, "Failed to read style content", e); return; } finally { try { source.close(); } catch(IOException e) {} } if (!mapStylesCollection.addStyleFromByteArray( SwigUtilities.createQByteArrayAsCopyOf(content), name)) { Log.w(TAG, "Failed to add style from byte array"); } } }