diff --git a/.gitignore b/.gitignore index 825f06d..73d5162 100755 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ DS_Store .cxx local.properties /legacy_code/ +*osma.style.json diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 438c4aa..b0f2995 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/app/src/main/java/eu/konggdev/strikemaps/factory/AlertDialogFactory.java b/app/src/main/java/eu/konggdev/strikemaps/factory/AlertDialogFactory.java index b55c015..3cefcb0 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/factory/AlertDialogFactory.java +++ b/app/src/main/java/eu/konggdev/strikemaps/factory/AlertDialogFactory.java @@ -13,8 +13,9 @@ import org.maplibre.geojson.Feature; import java.util.List; import java.util.function.Consumer; -//FIXME: Move Item functions into specific classes for specific types - e.g. StyleItem public final class AlertDialogFactory { + private AlertDialogFactory() {} // prevent instantiation + public static AlertDialog pointSelector(AppController app, List features, Consumer callback) { LinearLayout layout = new LinearLayout(app.getActivity()); layout.setOrientation(LinearLayout.VERTICAL); diff --git a/app/src/main/java/eu/konggdev/strikemaps/helper/FileHelper.java b/app/src/main/java/eu/konggdev/strikemaps/helper/FileHelper.java index 3228ab8..2b889c4 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/helper/FileHelper.java +++ b/app/src/main/java/eu/konggdev/strikemaps/helper/FileHelper.java @@ -10,8 +10,10 @@ import java.util.List; import eu.konggdev.strikemaps.app.AppController; -//FIXME: Ugly +//FIXME: Spaghetti code public final class FileHelper { + private FileHelper() {} // prevent instantiation + public static String loadStringFromAssetFile(String filePath, AppController app) { try (InputStream is = app.getActivity().getAssets().open(filePath)) { int size = is.available(); @@ -106,4 +108,4 @@ public final class FileHelper { return fileList.toArray(new String[0]); } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/MapComponent.java b/app/src/main/java/eu/konggdev/strikemaps/map/MapComponent.java index 422c4cf..23b78c0 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/MapComponent.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/MapComponent.java @@ -5,6 +5,7 @@ import eu.konggdev.strikemaps.Component; import eu.konggdev.strikemaps.factory.AlertDialogFactory; import eu.konggdev.strikemaps.helper.UserPrefsHelper; import eu.konggdev.strikemaps.map.renderer.implementation.VtmRenderer; +import eu.konggdev.strikemaps.map.style.MapStyle; import org.maplibre.android.geometry.LatLng; import org.maplibre.geojson.Feature; @@ -17,9 +18,9 @@ import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutCont public class MapComponent implements Component { MapRenderer mapRenderer; AppController app; - - public String style; + private MapStyle currentStyle; public Map, MapOverlay> overlays = new HashMap<>(); + public MapComponent(AppController ref) { this.app = ref; switch(UserPrefsHelper.mapRenderer(app.getPrefs())) { @@ -37,9 +38,14 @@ public class MapComponent implements Component { return new FragmentLayoutContentMap(mapRenderer.getView()); } - public void setStyle(String style) { - this.style = style; - mapRenderer.reload(); + public void setStyle(MapStyle style) { + if(style != null) + this.currentStyle = style; + update(); + } + + public MapStyle getStyle() { + return currentStyle; } public void switchOverlay(MapOverlay overlay) { @@ -65,7 +71,7 @@ public class MapComponent implements Component { } public void update() { - if(mapRenderer != null && style != null) mapRenderer.reload(); + if(mapRenderer != null && currentStyle != null) mapRenderer.reload(); } public boolean onMapClick(LatLng point) { diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/layer/MapLayer.java b/app/src/main/java/eu/konggdev/strikemaps/map/layer/MapLayer.java new file mode 100644 index 0000000..0096f64 --- /dev/null +++ b/app/src/main/java/eu/konggdev/strikemaps/map/layer/MapLayer.java @@ -0,0 +1,14 @@ +package eu.konggdev.strikemaps.map.layer; + +import org.maplibre.android.style.layers.Layer; +import org.maplibre.android.style.sources.GeoJsonSource; + +//FIXME: Stop being MapLibre reliant +public class MapLayer { + public GeoJsonSource source; + public Layer layer; + public MapLayer(GeoJsonSource source, Layer layer) { + this.source = source; + this.layer = layer; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/overlay/MapOverlay.java b/app/src/main/java/eu/konggdev/strikemaps/map/overlay/MapOverlay.java index 3d55939..c97a4ad 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/overlay/MapOverlay.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/overlay/MapOverlay.java @@ -1,6 +1,7 @@ package eu.konggdev.strikemaps.map.overlay; import eu.konggdev.strikemaps.map.layer.MapLayer; +import org.json.JSONException; /* More or less a data-driven layer factory */ public interface MapOverlay { diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/overlay/overlay/LocationOverlay.java b/app/src/main/java/eu/konggdev/strikemaps/map/overlay/overlay/LocationOverlay.java index a4277be..5a05057 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/overlay/overlay/LocationOverlay.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/overlay/overlay/LocationOverlay.java @@ -9,8 +9,12 @@ import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.layer.MapLayer; import eu.konggdev.strikemaps.map.overlay.MapOverlay; +import eu.konggdev.strikemaps.map.source.MapSource; import eu.konggdev.strikemaps.provider.LocationDataProvider; +import org.json.JSONException; +import org.json.JSONObject; import org.maplibre.android.style.layers.CircleLayer; +import org.maplibre.android.style.layers.Layer; import org.maplibre.android.style.layers.Property; import org.maplibre.android.style.sources.GeoJsonSource; import org.maplibre.geojson.Feature; diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreNativeRenderer.java b/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreNativeRenderer.java index a8da472..93efedf 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreNativeRenderer.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/renderer/implementation/MapLibreNativeRenderer.java @@ -4,17 +4,22 @@ import android.view.View; import androidx.annotation.NonNull; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import eu.konggdev.strikemaps.helper.FileHelper; import eu.konggdev.strikemaps.helper.UserPrefsHelper; import eu.konggdev.strikemaps.map.overlay.MapOverlay; import eu.konggdev.strikemaps.map.layer.MapLayer; import eu.konggdev.strikemaps.map.renderer.MapRenderer; +import eu.konggdev.strikemaps.map.style.MapStyle; +import org.json.JSONException; import org.maplibre.android.MapLibre; import org.maplibre.android.geometry.LatLng; import org.maplibre.android.maps.MapLibreMap; import org.maplibre.android.maps.MapView; import org.maplibre.android.maps.OnMapReadyCallback; import org.maplibre.android.maps.Style; +import org.maplibre.android.style.layers.Layer; import org.maplibre.geojson.Feature; import java.util.List; @@ -44,11 +49,21 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback { @Override public void reload() { - map.setStyle(new Style.Builder().fromJson(controller.style), style -> { - for(MapOverlay overlay : controller.overlays.values()) { - passLayer(overlay.makeLayer()); - } - }); + ObjectMapper mapper = new ObjectMapper(); + try { + MapStyle style = controller.getStyle(); + ObjectNode root = style.metadata.deepCopy(); + root.set("sources", mapper.valueToTree(style.sources)); + root.set("layers", style.layerDefinitions); + map.setStyle(new Style.Builder().fromJson(mapper.writeValueAsString(root)), intStyle -> { + for(MapOverlay overlay : controller.overlays.values()) { + passLayer(overlay.makeLayer()); + } + }); + } catch ( Exception e ) { + e.printStackTrace(); + } + } @Override @@ -65,7 +80,7 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback { public void onMapReady(@NonNull MapLibreMap maplibreMap) { this.map = maplibreMap; - controller.style = FileHelper.loadStringFromAssetFile(UserPrefsHelper.startupMapStyle(app.getPrefs()), app); + controller.setStyle(MapStyle.fromMapLibreJsonFile(UserPrefsHelper.startupMapStyle(app.getPrefs()), app)); //I have my own implementation of attribution that credits MapLibre among others, it's not as bad as it looks :) map.getUiSettings().setLogoEnabled(false); @@ -73,6 +88,6 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback { map.addOnMapClickListener(point -> controller.onMapClick(point)); map.addOnMapLongClickListener(point -> controller.onMapLongClick(point)); - this.reload(); + //this.reload(); } } diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/source/MapSource.java b/app/src/main/java/eu/konggdev/strikemaps/map/source/MapSource.java index 22fcb50..b6a4ed2 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/source/MapSource.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/source/MapSource.java @@ -1,12 +1,15 @@ package eu.konggdev.strikemaps.map.source; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.JsonNode; + +@JsonIgnoreProperties(ignoreUnknown = true) public class MapSource { - public final String url; - public final String type; - public final String schema; - public MapSource(String url, String type, String schema) { - this.url = url; - this.type = type; - this.schema = schema; - } + public String type; + public String url; + public String schema; + + @JsonIgnore + public JsonNode raw; } diff --git a/app/src/main/java/eu/konggdev/strikemaps/map/style/MapStyle.java b/app/src/main/java/eu/konggdev/strikemaps/map/style/MapStyle.java index 943bfd6..27209b3 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/map/style/MapStyle.java +++ b/app/src/main/java/eu/konggdev/strikemaps/map/style/MapStyle.java @@ -1,25 +1,28 @@ package eu.konggdev.strikemaps.map.style; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.helper.FileHelper; import eu.konggdev.strikemaps.map.layer.MapLayer; import eu.konggdev.strikemaps.map.source.MapSource; +import org.json.JSONObject; + import java.util.*; - public class MapStyle { - public int version; + //Only local data public String name; - public String icon; - public String description; + public Bitmap icon; + public JsonNode metadata; // everything except layers + sources public Map sources; - public List layers; - - public JsonNode raw; + public JsonNode layerDefinitions; // the "layers" array //FIXME public static MapStyle fromMapLibreJsonFile(String filename, AppController app) { @@ -30,34 +33,38 @@ public class MapStyle { ObjectMapper mapper = new ObjectMapper(); try { JsonNode root = mapper.readTree(styleContents); - MapStyle style = new MapStyle(); - style.version = root.path("version").asInt(); + MapStyle style = new MapStyle(); style.name = root.path("name").asText(); - style.icon = root.path("icon").asText(); - style.description = root.path("description").asText(); + style.icon = getIcon(root.path("icon").asText(), app); style.sources = mapper.convertValue( root.path("sources"), new TypeReference>() {} ); - style.layers = new ArrayList<>(); - for (JsonNode layerNode : root.path("layers")) { + style.layerDefinitions = root.path("layers"); - MapLayer layer = mapper.treeToValue(layerNode, MapLayer.class); - - layer.raw = layerNode; // IMPORTANT - - style.layers.add(layer); - } - - style.raw = root; // full backup + ObjectNode metadata = root.deepCopy(); + metadata.remove("layers"); + metadata.remove("sources"); + style.metadata = metadata; return style; - } catch ( Exception e ) { + } catch (Exception e) { e.printStackTrace(); } return null; } + + public static Bitmap getIcon(String iconLocator, AppController app) { + switch(iconLocator.split("//")[0]) { + //TODO: https + case "assets:": + return BitmapFactory.decodeStream(FileHelper.openAssetStream("bundled/icon/" + iconLocator.split("//")[1], app)); + default: + app.logcat("Unimplemented icon locator space: " + iconLocator); + return null; + } + } } diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/GenericItem.java b/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/GenericItem.java index af2512e..ec022a2 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/GenericItem.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/element/item/GenericItem.java @@ -1,26 +1,22 @@ package eu.konggdev.strikemaps.ui.element.item; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.view.View; import android.widget.ImageButton; import android.widget.TextView; import androidx.annotation.NonNull; import eu.konggdev.strikemaps.R; -import eu.konggdev.strikemaps.app.AppController; -import eu.konggdev.strikemaps.helper.FileHelper; import eu.konggdev.strikemaps.map.MapComponent; +import eu.konggdev.strikemaps.map.style.MapStyle; import eu.konggdev.strikemaps.ui.UIComponent; -import org.json.JSONObject; - -import java.io.InputStream; public class GenericItem implements UIItem { @NonNull public String name; public Bitmap image; public Runnable onClick; boolean hasImage; + public GenericItem(String refName) { this.name = refName; hasImage = false; @@ -41,29 +37,10 @@ public class GenericItem implements UIItem { this.onClick = onClick; hasImage = true; } - - //FIXME: Ugly glue static constructor - public final static GenericItem fromStyle(String style, AppController app, MapComponent map) { - try { - JSONObject styleJson = new JSONObject(style); - String name = "Unknown"; //Fallback name - if (styleJson.has("name")) name = styleJson.getString("name"); - if (styleJson.has("icon")) { - switch(styleJson.getString("icon").split("//")[0]) { - //TODO: https - case "assets:": - Bitmap icon = BitmapFactory.decodeStream(FileHelper.openAssetStream("bundled/icon/" + styleJson.getString("icon").split("//")[1], app)); - return new GenericItem(name, icon, () -> map.setStyle(style)); - default: - app.logcat("Unimplemented icon source requested in style: " + name); - return new GenericItem(name, () -> map.setStyle(style)); - } - } - return new GenericItem(name, () -> map.setStyle(style)); - } catch (Exception e) { - e.printStackTrace(); - return new GenericItem("Exception!", () -> map.setStyle(style)); - } + public final static GenericItem fromStyle(MapStyle style, MapComponent map) { + //if(style.icon != null) + //return new GenericItem(style.name, () -> map.setStyle(style)); + return new GenericItem(style.name, () -> map.setStyle(style)); } public View makeView(UIComponent spawner) { View v = spawner.inflateUi(R.layout.item_generic); diff --git a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/popup/FragmentMapChangePopup.java b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/popup/FragmentMapChangePopup.java index 8bf547f..aa56f92 100644 --- a/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/popup/FragmentMapChangePopup.java +++ b/app/src/main/java/eu/konggdev/strikemaps/ui/fragment/popup/FragmentMapChangePopup.java @@ -7,18 +7,24 @@ import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import android.widget.LinearLayout; +import java.util.stream.Stream; +import java.util.stream.Collectors; + import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.helper.FileHelper; import eu.konggdev.strikemaps.factory.AlertDialogFactory; import eu.konggdev.strikemaps.map.MapComponent; +import eu.konggdev.strikemaps.map.style.MapStyle; import eu.konggdev.strikemaps.ui.UIComponent; import eu.konggdev.strikemaps.ui.element.item.GenericItem; import org.apache.commons.lang3.ArrayUtils; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; public class FragmentMapChangePopup extends Fragment implements Popup { @@ -35,6 +41,7 @@ public class FragmentMapChangePopup extends Fragment implements Popup { this.ui = app.getUi(); this.region = region; } + @Override public Integer getRegion() { return region; @@ -50,12 +57,10 @@ public class FragmentMapChangePopup extends Fragment implements Popup { //FIXME setupButton(view, R.id.closeButton, click(() -> ui.getCurrentScreen().closePopup())); setupDragHandle(view, view, () -> ui.getCurrentScreen().closePopup()); - String[] stylePaths = ArrayUtils.addAll(FileHelper.getAssetFiles("bundled/style", ".style.json", app), FileHelper.getUserFiles("style", ".style.json", app)); - List views = new ArrayList<>(); - LinearLayout stylesLayout = view.findViewById(R.id.stylesLayout); - for(String i : stylePaths) { - if(i.startsWith("/storage")) stylesLayout.addView(GenericItem.fromStyle(FileHelper.loadStringFromUserFile(i), app, map).makeView(ui)); - else stylesLayout.addView(GenericItem.fromStyle(FileHelper.loadStringFromAssetFile(i, app), app, map).makeView(ui)); - } + List stylePaths = Stream.concat( + Arrays.stream(FileHelper.getAssetFiles("bundled/style", ".style.json", app)), + Arrays.stream(FileHelper.getUserFiles("style", ".style.json", app)) + ).collect(Collectors.toList()); LinearLayout stylesLayout = view.findViewById(R.id.stylesLayout); + stylePaths.forEach(style -> stylesLayout.addView(GenericItem.fromStyle(MapStyle.fromMapLibreJsonFile(style, app), map).makeView(ui))); } }