package com.mapbox.mapboxsdk.maps; import android.graphics.Bitmap; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StringDef; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; import com.mapbox.mapboxsdk.style.light.Light; import com.mapbox.mapboxsdk.style.sources.Source; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * The proxy object for current map style. *

* To create new instances of this object, create a new instance using a {@link Builder} and load the style with * {@link MapboxMap#setStyle(Builder)}. This object is returned from {@link MapboxMap#getStyle()} once the style * has been loaded by underlying map. *

*/ public class Style { private final NativeMapView nativeMapView; private final HashMap sources = new HashMap<>(); private final HashMap layers = new HashMap<>(); private final HashMap images = new HashMap<>(); private final Builder builder; private boolean styleLoaded; /** * Private constructor to build a style object. * * @param builder the builder used for creating this style * @param nativeMapView the map object used to load this style */ private Style(@NonNull Builder builder, @NonNull NativeMapView nativeMapView) { this.builder = builder; this.nativeMapView = nativeMapView; } /** * Returns the current style url. * * @return the style url */ @NonNull public String getUrl() { return nativeMapView.getStyleUrl(); } /** * Returns the current style json. * * @return the style json */ @NonNull public String getJson() { return nativeMapView.getStyleJson(); } // // Source // /** * Retrieve all the sources in the style * * @return all the sources in the current style */ @NonNull public List getSources() { return nativeMapView.getSources(); } /** * Adds the source to the map. The source must be newly created and not added to the map before * * @param source the source to add */ public void addSource(@NonNull Source source) { sources.put(source.getId(), source); nativeMapView.addSource(source); } /** * Retrieve a source by id * * @param id the source's id * @return the source if present in the current style */ @Nullable public Source getSource(String id) { Source source = sources.get(id); if (source == null) { source = nativeMapView.getSource(id); } return source; } /** * Tries to cast the Source to T, throws ClassCastException if it's another type. * * @param sourceId the id used to look up a layer * @param the generic type of a Source * @return the casted Source, null if another type */ @Nullable public T getSourceAs(@NonNull String sourceId) { // noinspection unchecked if (sources.containsKey(sourceId)) { return (T) sources.get(sourceId); } return (T) nativeMapView.getSource(sourceId); } /** * Removes the source from the style. * * @param sourceId the source to remove * @return the source handle or null if the source was not present */ @Nullable public boolean removeSource(@NonNull String sourceId) { sources.remove(sourceId); return nativeMapView.removeSource(sourceId); } /** * Removes the source, preserving the reference for re-use * * @param source the source to remove * @return the source */ @Nullable public boolean removeSource(@NonNull Source source) { sources.remove(source.getId()); return nativeMapView.removeSource(source); } // // Layer // /** * Adds the layer to the map. The layer must be newly created and not added to the map before * * @param layer the layer to add */ public void addLayer(@NonNull Layer layer) { layers.put(layer.getId(), layer); nativeMapView.addLayer(layer); } /** * Adds the layer to the map. The layer must be newly created and not added to the map before * * @param layer the layer to add * @param below the layer id to add this layer before */ public void addLayerBelow(@NonNull Layer layer, @NonNull String below) { layers.put(layer.getId(), layer); nativeMapView.addLayerBelow(layer, below); } /** * Adds the layer to the map. The layer must be newly created and not added to the map before * * @param layer the layer to add * @param above the layer id to add this layer above */ public void addLayerAbove(@NonNull Layer layer, @NonNull String above) { layers.put(layer.getId(), layer); nativeMapView.addLayerAbove(layer, above); } /** * Adds the layer to the map at the specified index. The layer must be newly * created and not added to the map before * * @param layer the layer to add * @param index the index to insert the layer at */ public void addLayerAt(@NonNull Layer layer, @IntRange(from = 0) int index) { layers.put(layer.getId(), layer); nativeMapView.addLayerAt(layer, index); } /** * Get the layer by id * * @param id the layer's id * @return the layer, if present in the style */ @Nullable public Layer getLayer(@NonNull String id) { Layer layer = layers.get(id); if (layer == null) { layer = nativeMapView.getLayer(id); } return layer; } /** * Tries to cast the Layer to T, throws ClassCastException if it's another type. * * @param layerId the layer id used to look up a layer * @param the generic attribute of a Layer * @return the casted Layer, null if another type */ @Nullable public T getLayerAs(@NonNull String layerId) { // noinspection unchecked return (T) nativeMapView.getLayer(layerId); } /** * Retrieve all the layers in the style * * @return all the layers in the current style */ @NonNull public List getLayers() { return nativeMapView.getLayers(); } /** * Removes the layer. Any references to the layer become invalid and should not be used anymore * * @param layerId the layer to remove * @return the removed layer or null if not found */ public boolean removeLayer(@NonNull String layerId) { layers.remove(layerId); return nativeMapView.removeLayer(layerId); } /** * Removes the layer. The reference is re-usable after this and can be re-added * * @param layer the layer to remove * @return the layer */ public boolean removeLayer(@NonNull Layer layer) { layers.remove(layer.getId()); return nativeMapView.removeLayer(layer); } /** * Removes the layer. Any other references to the layer become invalid and should not be used anymore * * @param index the layer index * @return the removed layer or null if not found */ public boolean removeLayerAt(@IntRange(from = 0) int index) { return nativeMapView.removeLayerAt(index); } // // Image // /** * Adds an image to be used in the map's style * * @param name the name of the image * @param image the pre-multiplied Bitmap */ public void addImage(@NonNull String name, @NonNull Bitmap image) { addImage(name, image, false); } /** * Adds an image to be used in the map's style * * @param name the name of the image * @param image the pre-multiplied Bitmap * @param sdf the flag indicating image is an SDF or template image */ public void addImage(@NonNull String name, @NonNull Bitmap image, boolean sdf) { nativeMapView.addImage(name, image, sdf); } /** * Adds an images to be used in the map's style. */ public void addImages(@NonNull HashMap images) { nativeMapView.addImages(images); } /** * Removes an image from the map's style. * * @param name the name of the image to remove */ public void removeImage(@NonNull String name) { nativeMapView.removeImage(name); } /** * Get an image from the map's style using an id. * * @param id the id of the image * @return the image bitmap */ @Nullable public Bitmap getImage(@NonNull String id) { return nativeMapView.getImage(id); } // // Transition // /** *

* Set the transition duration for style changes. *

* The default value for delay and duration is zero, so any changes take effect without animation. * * @param transitionOptions the transition options */ public void setTransition(@NonNull TransitionOptions transitionOptions) { nativeMapView.setTransitionDuration(transitionOptions.getDuration()); nativeMapView.setTransitionDelay(transitionOptions.getDelay()); } /** *

* Get the transition for style changes. *

* The default value for delay and transition is zero, so any changes take effect without animation. * * @return TransitionOptions the transition options */ @NonNull public TransitionOptions getTransition() { return new TransitionOptions(nativeMapView.getTransitionDuration(), nativeMapView.getTransitionDelay()); } // // Light // /** * Get the light source used to change lighting conditions on extruded fill layers. * * @return the global light source */ @Nullable public Light getLight() { return nativeMapView.getLight(); } /** * Called when the underlying map will start loading a new style. This method will clean up this style * by setting the java sources and layers in a detached state and removing them from core. */ void onWillStartLoadingMap() { styleLoaded = false; for (Source source : sources.values()) { if (source != null) { source.setDetached(); nativeMapView.removeSource(source); } } for (Layer layer : layers.values()) { if (layer != null) { layer.setDetached(); nativeMapView.removeLayer(layer); } } for (Map.Entry bitmapEntry : images.entrySet()) { nativeMapView.removeImage(bitmapEntry.getKey()); bitmapEntry.getValue().recycle(); } sources.clear(); layers.clear(); images.clear(); } /** * Called when the underlying map has finished loading this style. * This method will add all components added to the builder that were defined with the 'with' prefix. */ void onDidFinishLoadingStyle() { if (!styleLoaded) { styleLoaded = true; for (Source source : builder.sources) { addSource(source); } for (Builder.LayerWrapper layerWrapper : builder.layers) { if (layerWrapper instanceof Builder.LayerAtWrapper) { addLayerAt(layerWrapper.layer, ((Builder.LayerAtWrapper) layerWrapper).index); } else if (layerWrapper instanceof Builder.LayerAboveWrapper) { addLayerAbove(layerWrapper.layer, ((Builder.LayerAboveWrapper) layerWrapper).aboveLayer); } else if (layerWrapper instanceof Builder.LayerBelowWrapper) { addLayerBelow(layerWrapper.layer, ((Builder.LayerBelowWrapper) layerWrapper).belowLayer); } else { // just add layer to map, but below annotations addLayerBelow(layerWrapper.layer, MapboxConstants.LAYER_ID_ANNOTATIONS); } } for (Builder.ImageWrapper image : builder.images) { addImage(image.id, image.bitmap, image.sdf); } if (builder.transitionOptions != null) { setTransition(builder.transitionOptions); } } } boolean isStyleLoaded() { return styleLoaded; } // // Builder // /** * Builder for composing a style object. */ public static class Builder { private final List sources = new ArrayList<>(); private final List layers = new ArrayList<>(); private final List images = new ArrayList<>(); private TransitionOptions transitionOptions; private String styleUrl; private String styleJson; /** *

* Will loads a new map style asynchronous from the specified URL. *

* {@code url} can take the following forms: *
    *
  • {@code Style#StyleUrl}: load one of the bundled styles in {@link Style}.
  • *
  • {@code mapbox://styles//