Compare commits

...

7 Commits

Author SHA1 Message Date
9c685e5e2e Update classic style 2026-04-16 17:23:30 +02:00
fbc8b8eedb Fix raster sources
Fix typo
2026-04-16 17:23:28 +02:00
92d685021b Update README.md 2026-04-15 23:09:28 +00:00
1e9a421df2 Small fixes 2026-04-16 01:08:09 +02:00
b4bc8feec5 Data namespace 2026-04-16 00:59:17 +02:00
4a625323d4 Refractor UI factories into UI namespace 2026-04-16 00:56:49 +02:00
9c9d74d5e4 Furhter work on MapStyle 2026-04-16 00:50:49 +02:00
19 changed files with 3046 additions and 148 deletions

View File

@@ -1,8 +1,8 @@
## Strike Maps Project Android Client ## Strike Maps Project Android Client
Strike Maps is a project hoping to create a multi-platform, <br> Strike Maps is a project hoping to create a multi-platform, <br>
user-facing, complete FOSS mapping stack, focusing on style customization <br> user-facing, complete FOSS mapping stack, focusing on customization <br>
and offline features. and offline features.
<br> <br>
This Android app has been in development since around January 2026 This Android app has been in development since around January 2026

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
package eu.konggdev.strikemaps.helper; package eu.konggdev.strikemaps.data.helper;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.os.Environment; import android.os.Environment;

View File

@@ -1,4 +1,4 @@
package eu.konggdev.strikemaps.helper; package eu.konggdev.strikemaps.data.helper;
import android.content.SharedPreferences; import android.content.SharedPreferences;

View File

@@ -1,4 +1,4 @@
package eu.konggdev.strikemaps.provider; package eu.konggdev.strikemaps.data.provider;
public class HttpDataProvider implements Provider { public class HttpDataProvider implements Provider {
//TODO //TODO

View File

@@ -1,4 +1,4 @@
package eu.konggdev.strikemaps.provider; package eu.konggdev.strikemaps.data.provider;
import android.Manifest; import android.Manifest;
import android.content.Context; import android.content.Context;

View File

@@ -0,0 +1,5 @@
package eu.konggdev.strikemaps.data.provider;
public interface Provider {
}

View File

@@ -2,9 +2,10 @@ package eu.konggdev.strikemaps.map;
import java.util.*; import java.util.*;
import eu.konggdev.strikemaps.Component; import eu.konggdev.strikemaps.Component;
import eu.konggdev.strikemaps.factory.AlertDialogFactory; import eu.konggdev.strikemaps.ui.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.helper.UserPrefsHelper; import eu.konggdev.strikemaps.data.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.renderer.implementation.VtmRenderer; import eu.konggdev.strikemaps.map.renderer.implementation.VtmRenderer;
import eu.konggdev.strikemaps.map.style.MapStyle;
import org.maplibre.android.geometry.LatLng; import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature; import org.maplibre.geojson.Feature;
@@ -18,7 +19,7 @@ public class MapComponent implements Component {
MapRenderer mapRenderer; MapRenderer mapRenderer;
AppController app; AppController app;
public String style; public MapStyle style;
public Map<Class<? extends MapOverlay>, MapOverlay> overlays = new HashMap<>(); public Map<Class<? extends MapOverlay>, MapOverlay> overlays = new HashMap<>();
public MapComponent(AppController ref) { public MapComponent(AppController ref) {
this.app = ref; this.app = ref;
@@ -37,7 +38,7 @@ public class MapComponent implements Component {
return new FragmentLayoutContentMap(mapRenderer.getView()); return new FragmentLayoutContentMap(mapRenderer.getView());
} }
public void setStyle(String style) { public void setStyle(MapStyle style) {
this.style = style; this.style = style;
mapRenderer.reload(); mapRenderer.reload();
} }

View File

@@ -0,0 +1,16 @@
package eu.konggdev.strikemaps.map.layer;
import org.maplibre.android.style.layers.Layer;
import org.maplibre.android.style.sources.GeoJsonSource;
//FIXME: Get rid of reliance on MapLibre!
//Most likely implement an "AdditionalMapLayer" or something of that sorts (?)
public class MapLayer {
public GeoJsonSource source;
public Layer layer;
public MapLayer(GeoJsonSource source, Layer layer) {
this.source = source;
this.layer = layer;
}
}

View File

@@ -1,4 +1,4 @@
package eu.konggdev.strikemaps.map.overlay.overlay; package eu.konggdev.strikemaps.map.overlay.implementation;
import android.graphics.Color; import android.graphics.Color;
import android.location.Location; import android.location.Location;
@@ -9,7 +9,7 @@ import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.layer.MapLayer; import eu.konggdev.strikemaps.map.layer.MapLayer;
import eu.konggdev.strikemaps.map.overlay.MapOverlay; import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.provider.LocationDataProvider; import eu.konggdev.strikemaps.data.provider.LocationDataProvider;
import org.maplibre.android.style.layers.CircleLayer; import org.maplibre.android.style.layers.CircleLayer;
import org.maplibre.android.style.layers.Property; import org.maplibre.android.style.layers.Property;
import org.maplibre.android.style.sources.GeoJsonSource; import org.maplibre.android.style.sources.GeoJsonSource;

View File

@@ -1,4 +1,4 @@
package eu.konggdev.strikemaps.map.overlay.overlay; package eu.konggdev.strikemaps.map.overlay.implementation;
import eu.konggdev.strikemaps.map.layer.MapLayer; import eu.konggdev.strikemaps.map.layer.MapLayer;
import eu.konggdev.strikemaps.map.overlay.MapOverlay; import eu.konggdev.strikemaps.map.overlay.MapOverlay;

View File

@@ -4,11 +4,13 @@ import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.helper.FileHelper; import com.fasterxml.jackson.databind.ObjectMapper;
import eu.konggdev.strikemaps.helper.UserPrefsHelper; import com.fasterxml.jackson.databind.node.ObjectNode;
import eu.konggdev.strikemaps.data.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.overlay.MapOverlay; import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.layer.MapLayer; import eu.konggdev.strikemaps.map.layer.MapLayer;
import eu.konggdev.strikemaps.map.renderer.MapRenderer; import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import eu.konggdev.strikemaps.map.style.MapStyle;
import org.maplibre.android.MapLibre; import org.maplibre.android.MapLibre;
import org.maplibre.android.geometry.LatLng; import org.maplibre.android.geometry.LatLng;
import org.maplibre.android.maps.MapLibreMap; import org.maplibre.android.maps.MapLibreMap;
@@ -44,11 +46,20 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
@Override @Override
public void reload() { public void reload() {
map.setStyle(new Style.Builder().fromJson(controller.style), style -> { ObjectMapper mapper = new ObjectMapper();
for(MapOverlay overlay : controller.overlays.values()) { MapStyle style = controller.style;
passLayer(overlay.makeLayer()); try {
} 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 @Override
@@ -65,7 +76,7 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
public void onMapReady(@NonNull MapLibreMap maplibreMap) { public void onMapReady(@NonNull MapLibreMap maplibreMap) {
this.map = 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 :) //I have my own implementation of attribution that credits MapLibre among others, it's not as bad as it looks :)
map.getUiSettings().setLogoEnabled(false); map.getUiSettings().setLogoEnabled(false);
@@ -73,6 +84,5 @@ public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
map.addOnMapClickListener(point -> controller.onMapClick(point)); map.addOnMapClickListener(point -> controller.onMapClick(point));
map.addOnMapLongClickListener(point -> controller.onMapLongClick(point)); map.addOnMapLongClickListener(point -> controller.onMapLongClick(point));
this.reload();
} }
} }

View File

@@ -1,12 +1,18 @@
package eu.konggdev.strikemaps.map.source; package eu.konggdev.strikemaps.map.source;
import com.fasterxml.jackson.databind.JsonNode;
public class MapSource { public class MapSource {
public final String url; public String url;
public final String type; public String type;
public final String schema; public String schema;
public MapSource(String url, String type, String schema) {
this.url = url; public String attribution;
this.type = type;
this.schema = schema; /* For raster sources */
} public JsonNode tiles;
public int minzoom;
public int maxzoom;
public MapSource() { }
} }

View File

@@ -1,25 +1,25 @@
package eu.konggdev.strikemaps.map.style; package eu.konggdev.strikemaps.map.style;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.helper.FileHelper; import eu.konggdev.strikemaps.data.helper.FileHelper;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import eu.konggdev.strikemaps.map.source.MapSource; import eu.konggdev.strikemaps.map.source.MapSource;
import java.util.*; import java.util.*;
public class MapStyle { public class MapStyle {
public int version; //Only local data
public String name; public String name;
public String icon; public Bitmap icon;
public String description;
public JsonNode metadata; // everything except layers + sources
public Map<String, MapSource> sources; public Map<String, MapSource> sources;
public List<MapLayer> layers; public JsonNode layerDefinitions; // the "layers" array
public JsonNode raw;
//FIXME //FIXME
public static MapStyle fromMapLibreJsonFile(String filename, AppController app) { public static MapStyle fromMapLibreJsonFile(String filename, AppController app) {
@@ -30,34 +30,38 @@ public class MapStyle {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
try { try {
JsonNode root = mapper.readTree(styleContents); 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.name = root.path("name").asText();
style.icon = root.path("icon").asText(); style.icon = getIcon(root.path("icon").asText(), app);
style.description = root.path("description").asText();
style.sources = mapper.convertValue( style.sources = mapper.convertValue(
root.path("sources"), root.path("sources"),
new TypeReference<Map<String, MapSource>>() {} new TypeReference<Map<String, MapSource>>() {}
); );
style.layers = new ArrayList<>(); style.layerDefinitions = root.path("layers");
for (JsonNode layerNode : root.path("layers")) {
MapLayer layer = mapper.treeToValue(layerNode, MapLayer.class); ObjectNode metadata = root.deepCopy();
metadata.remove("layers");
layer.raw = layerNode; // IMPORTANT metadata.remove("sources");
style.metadata = metadata;
style.layers.add(layer);
}
style.raw = root; // full backup
return style; return style;
} catch ( Exception e ) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return null; 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;
}
}
} }

View File

@@ -1,5 +0,0 @@
package eu.konggdev.strikemaps.provider;
public interface Provider {
}

View File

@@ -1,26 +1,22 @@
package eu.konggdev.strikemaps.ui.element.item; package eu.konggdev.strikemaps.ui.element.item;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View; import android.view.View;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.R; 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.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.ui.UIComponent; import eu.konggdev.strikemaps.ui.UIComponent;
import org.json.JSONObject;
import java.io.InputStream;
public class GenericItem implements UIItem { public class GenericItem implements UIItem {
@NonNull public String name; @NonNull public String name;
public Bitmap image; public Bitmap image;
public Runnable onClick; public Runnable onClick;
boolean hasImage; boolean hasImage;
public GenericItem(String refName) { public GenericItem(String refName) {
this.name = refName; this.name = refName;
hasImage = false; hasImage = false;
@@ -42,28 +38,11 @@ public class GenericItem implements UIItem {
hasImage = true; hasImage = true;
} }
//FIXME: Ugly glue static constructor public final static GenericItem fromStyle(MapStyle style, MapComponent map) {
public final static GenericItem fromStyle(String style, AppController app, MapComponent map) { if(style == null) return new GenericItem("Unknown");
try { if(style.icon != null)
JSONObject styleJson = new JSONObject(style); return new GenericItem(style.name, style.icon, () -> map.setStyle(style));
String name = "Unknown"; //Fallback name return new GenericItem(style.name, () -> map.setStyle(style));
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 View makeView(UIComponent spawner) { public View makeView(UIComponent spawner) {
View v = spawner.inflateUi(R.layout.item_generic); View v = spawner.inflateUi(R.layout.item_generic);

View File

@@ -1,4 +1,4 @@
package eu.konggdev.strikemaps.factory; package eu.konggdev.strikemaps.ui.factory;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.graphics.Color; import android.graphics.Color;

View File

@@ -15,8 +15,8 @@ import android.widget.Toast;
import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.helper.UserPrefsHelper; import eu.konggdev.strikemaps.data.helper.UserPrefsHelper;
import eu.konggdev.strikemaps.map.overlay.overlay.LocationOverlay; import eu.konggdev.strikemaps.map.overlay.implementation.LocationOverlay;
import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup; import eu.konggdev.strikemaps.ui.fragment.popup.FragmentMapChangePopup;
public class FragmentLayoutControls extends Fragment implements Layout { public class FragmentLayoutControls extends Fragment implements Layout {

View File

@@ -9,15 +9,15 @@ import android.widget.LinearLayout;
import eu.konggdev.strikemaps.R; import eu.konggdev.strikemaps.R;
import eu.konggdev.strikemaps.app.AppController; import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.helper.FileHelper; import eu.konggdev.strikemaps.data.helper.FileHelper;
import eu.konggdev.strikemaps.factory.AlertDialogFactory;
import eu.konggdev.strikemaps.map.MapComponent; import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.style.MapStyle;
import eu.konggdev.strikemaps.ui.UIComponent; import eu.konggdev.strikemaps.ui.UIComponent;
import eu.konggdev.strikemaps.ui.element.item.GenericItem; import eu.konggdev.strikemaps.ui.element.item.GenericItem;
import org.apache.commons.lang3.ArrayUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -35,6 +35,7 @@ public class FragmentMapChangePopup extends Fragment implements Popup {
this.ui = app.getUi(); this.ui = app.getUi();
this.region = region; this.region = region;
} }
@Override @Override
public Integer getRegion() { public Integer getRegion() {
return region; return region;
@@ -50,12 +51,11 @@ public class FragmentMapChangePopup extends Fragment implements Popup {
//FIXME //FIXME
setupButton(view, R.id.closeButton, click(() -> ui.getCurrentScreen().closePopup())); setupButton(view, R.id.closeButton, click(() -> ui.getCurrentScreen().closePopup()));
setupDragHandle(view, view, () -> 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<String> stylePaths = new ArrayList<>();
List<View> views = new ArrayList<>(); stylePaths.addAll(Arrays.asList(FileHelper.getAssetFiles("bundled/style", ".style.json", app)));
stylePaths.addAll(Arrays.asList(FileHelper.getUserFiles("style", ".style.json", app)));
LinearLayout stylesLayout = view.findViewById(R.id.stylesLayout); LinearLayout stylesLayout = view.findViewById(R.id.stylesLayout);
for(String i : stylePaths) { for (String style : stylePaths)
if(i.startsWith("/storage")) stylesLayout.addView(GenericItem.fromStyle(FileHelper.loadStringFromUserFile(i), app, map).makeView(ui)); stylesLayout.addView(GenericItem.fromStyle(MapStyle.fromMapLibreJsonFile(style, app), map).makeView(ui));
else stylesLayout.addView(GenericItem.fromStyle(FileHelper.loadStringFromAssetFile(i, app), app, map).makeView(ui));
}
} }
} }