This commit is contained in:
2026-04-13 20:19:02 +02:00
commit 31c92aeae9
77 changed files with 4610 additions and 0 deletions
@@ -0,0 +1,95 @@
package eu.konggdev.strikemaps.map;
import java.util.*;
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 org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.map.renderer.implementation.MapLibreNativeRenderer;
import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import eu.konggdev.strikemaps.ui.fragment.layout.content.main.FragmentLayoutContentMap;
public class MapComponent implements Component {
MapRenderer mapRenderer;
AppController app;
public String style;
public Map<Class<? extends MapOverlay>, MapOverlay> overlays = new HashMap<>();
public MapComponent(AppController ref) {
this.app = ref;
switch(UserPrefsHelper.mapRenderer(app.getPrefs())) {
case "vtm":
this.mapRenderer = new VtmRenderer(app, this);
break;
case "mapLibre":
default: //This shouldn't happen
this.mapRenderer = new MapLibreNativeRenderer(app, this);
break;
};
}
public FragmentLayoutContentMap toFragment() {
return new FragmentLayoutContentMap(mapRenderer.getView());
}
public void setStyle(String style) {
this.style = style;
mapRenderer.reload();
}
public void switchOverlay(MapOverlay overlay) {
if (hasOverlay(overlay)) overlays.remove(overlay.getClass());
else overlays.put(overlay.getClass(), overlay);
update();
}
public boolean hasOverlay(MapOverlay overlay) {
return overlays.containsKey(overlay.getClass());
}
public boolean hasOverlay(Class<? extends MapOverlay> overlay) {
return overlays.containsKey(overlay);
}
public void selectPoint(Feature selection) {
//FIXME: Put back FragmentPointPreviewPopup (private code atm)
}
public void onOverlayUpdate() {
update();
}
public void update() {
if(mapRenderer != null && style != null) mapRenderer.reload();
}
public boolean onMapClick(LatLng point) {
List<Feature> features = mapRenderer.featuresAtPoint(point);
switch (features.size()) {
case 0:
//TODO: Implement point selection for no POI found (MIGHT be done on long click??)
//Maybe collapse UI? (Hide/show UI feature)... could be user configurable
break;
case 1:
selectPoint(features.get(0));
break;
default:
app.getUi().alert(
AlertDialogFactory.pointSelector(app, features, selectedItem -> {
selectPoint(selectedItem);
}));
}
return true;
}
public boolean onMapLongClick(LatLng point) {
//TODO: Likely Nonfeature(?) point selection
return true;
}
}
@@ -0,0 +1,16 @@
package eu.konggdev.strikemaps.map.layer;
import org.maplibre.android.style.layers.Layer;
import org.maplibre.android.style.sources.GeoJsonSource;
import java.util.List;
//TOOD: Make not strictly MapLibre reliant
public class MapLayer {
public GeoJsonSource source;
public Layer layer;
public MapLayer(GeoJsonSource source, Layer layer) {
this.source = source;
this.layer = layer;
}
}
@@ -0,0 +1,8 @@
package eu.konggdev.strikemaps.map.overlay;
import eu.konggdev.strikemaps.map.layer.MapLayer;
/* More or less a data-driven layer factory */
public interface MapOverlay {
public MapLayer makeLayer();
}
@@ -0,0 +1,62 @@
package eu.konggdev.strikemaps.map.overlay.overlay;
import android.graphics.Color;
import android.location.Location;
import android.location.LocationListener;
import androidx.annotation.NonNull;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
import eu.konggdev.strikemaps.provider.LocationDataProvider;
import org.maplibre.android.style.layers.CircleLayer;
import org.maplibre.android.style.layers.Property;
import org.maplibre.android.style.sources.GeoJsonSource;
import org.maplibre.geojson.Feature;
import org.maplibre.geojson.FeatureCollection;
import org.maplibre.geojson.Point;
import static org.maplibre.android.style.layers.PropertyFactory.*;
public class LocationOverlay implements MapOverlay, LocationListener {
LocationDataProvider locationDataProvider;
AppController app;
MapComponent map;
public Location currentLocation = null;
public LocationOverlay(AppController app) {
this.app = app;
this.map = app.getMap();
this.locationDataProvider = new LocationDataProvider(app.getActivity(), this);
}
@Override
public MapLayer makeLayer() {
GeoJsonSource source = new GeoJsonSource(
"location",
FeatureCollection.fromFeatures(new Feature[]{}) // empty
);
if (currentLocation != null)
source.setGeoJson(Feature.fromGeometry(Point.fromLngLat(currentLocation.getLongitude(), currentLocation.getLatitude())));
CircleLayer layer = new CircleLayer("location", "location");
layer.setProperties(
circleRadius(5f),
circleColor(Color.parseColor("#1E88E5")),
circleStrokeColor(Color.WHITE),
circleStrokeWidth(1.5f),
circlePitchAlignment(Property.CIRCLE_PITCH_ALIGNMENT_MAP)
);
return new MapLayer(source, layer);
}
@Override
public void onLocationChanged(@NonNull Location location) {
this.currentLocation = location;
map.onOverlayUpdate();
}
}
@@ -0,0 +1,11 @@
package eu.konggdev.strikemaps.map.overlay.overlay;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import eu.konggdev.strikemaps.map.overlay.MapOverlay;
public class PointSelectionOverlay implements MapOverlay {
@Override
public MapLayer makeLayer() {
return null;
}
}
@@ -0,0 +1,19 @@
package eu.konggdev.strikemaps.map.renderer;
import android.view.View;
import android.view.ViewGroup;
import eu.konggdev.strikemaps.map.layer.MapLayer;
import org.maplibre.android.style.layers.Layer;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature;
import java.util.List;
public interface MapRenderer {
void reload();
View getView();
List<Feature> featuresAtPoint(LatLng point);
}
@@ -0,0 +1,78 @@
package eu.konggdev.strikemaps.map.renderer.implementation;
import android.view.View;
import androidx.annotation.NonNull;
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 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.geojson.Feature;
import java.util.List;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
public class MapLibreNativeRenderer implements MapRenderer, OnMapReadyCallback {
AppController app;
MapComponent controller;
MapLibreMap map;
final MapView mapView;
public MapLibreNativeRenderer(AppController app, MapComponent controller) {
this.app = app;
this.controller = controller;
MapLibre.getInstance(app.getActivity());
this.mapView = new MapView(app.getActivity());
mapView.onCreate(null);
mapView.getMapAsync(this);
}
void passLayer(MapLayer layer) {
map.getStyle().addSource(layer.source);
map.getStyle().addLayer(layer.layer);
}
@Override
public void reload() {
map.setStyle(new Style.Builder().fromJson(controller.style), style -> {
for(MapOverlay overlay : controller.overlays.values()) {
passLayer(overlay.makeLayer());
}
});
}
@Override
public View getView() {
return mapView;
}
@Override
public List<Feature> featuresAtPoint(LatLng point) {
return map.queryRenderedFeatures(map.getProjection().toScreenLocation(point));
}
@Override
public void onMapReady(@NonNull MapLibreMap maplibreMap) {
this.map = maplibreMap;
controller.style = FileHelper.loadStringFromAssetFile(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);
map.getUiSettings().setAttributionEnabled(false);
map.addOnMapClickListener(point -> controller.onMapClick(point));
map.addOnMapLongClickListener(point -> controller.onMapLongClick(point));
this.reload();
}
}
@@ -0,0 +1,50 @@
package eu.konggdev.strikemaps.map.renderer.implementation;
import android.view.View;
import eu.konggdev.strikemaps.app.AppController;
import eu.konggdev.strikemaps.map.MapComponent;
import eu.konggdev.strikemaps.map.renderer.MapRenderer;
import okhttp3.OkHttpClient;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.geojson.Feature;
import org.oscim.android.MapView;
import org.oscim.map.Map;
import org.oscim.tiling.source.OkHttpEngine;
import org.oscim.tiling.source.oscimap4.OSciMap4TileSource;
import java.util.Collections;
import java.util.List;
public class VtmRenderer implements MapRenderer {
AppController app;
MapComponent controller;
Map map;
final MapView mapView;
public VtmRenderer(AppController app, MapComponent controller) {
this.app = app;
this.controller = controller;
this.mapView = new MapView(app.getActivity());
this.map = mapView.map();
}
@Override
public void reload() {
//TODO
OkHttpClient.Builder builder = new OkHttpClient.Builder();
OSciMap4TileSource tileSource = OSciMap4TileSource.builder().httpFactory(new OkHttpEngine.OkHttpFactory(builder)).build();
map.setBaseMap(tileSource);
}
@Override
public View getView() {
return mapView;
}
@Override
public List<Feature> featuresAtPoint(LatLng point) {
return Collections.emptyList();
}
}
@@ -0,0 +1,12 @@
package eu.konggdev.strikemaps.map.source;
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;
}
}