chore(mobile): replace maplibre_gl with maplibre

maplibre is a ground-up rewrite of maplibre_gl with a more modern and
ergonomic API. It should fix a few bugs we've seen with maps, and
perform better.
This commit is contained in:
Thomas Way
2026-02-21 01:17:06 +00:00
parent 82c6302549
commit c087b7c063
46 changed files with 666 additions and 651 deletions
@@ -1,20 +1,23 @@
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:maplibre/maplibre.dart';
extension WithinBounds on LatLngBounds {
extension WithinBounds on LngLatBounds {
/// Checks whether [point] is inside bounds
bool contains(LatLng point) {
final sw = point;
final ne = point;
return containsBounds(LatLngBounds(southwest: sw, northeast: ne));
bool contains(Geographic point) {
return containsBounds(
LngLatBounds(
longitudeWest: point.lon,
longitudeEast: point.lon,
latitudeSouth: point.lat,
latitudeNorth: point.lat,
),
);
}
/// Checks whether [bounds] is contained inside bounds
bool containsBounds(LatLngBounds bounds) {
final sw = bounds.southwest;
final ne = bounds.northeast;
return (sw.latitude >= southwest.latitude) &&
(ne.latitude <= northeast.latitude) &&
(sw.longitude >= southwest.longitude) &&
(ne.longitude <= northeast.longitude);
bool containsBounds(LngLatBounds bounds) {
return (bounds.latitudeSouth >= latitudeSouth) &&
(bounds.latitudeNorth <= latitudeNorth) &&
(bounds.longitudeWest >= longitudeWest) &&
(bounds.longitudeEast <= longitudeEast);
}
}
@@ -1,19 +1,19 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:immich_mobile/models/map/map_marker.model.dart';
import 'package:immich_mobile/utils/map_utils.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:maplibre/maplibre.dart';
extension MapMarkers on MapLibreMapController {
extension MapMarkers on MapController {
static var _completer = Completer()..complete();
Future<void> addGeoJSONSourceForMarkers(List<MapMarker> markers) async {
return addSource(
MapUtils.defaultSourceId,
GeojsonSourceProperties(data: MapUtils.generateGeoJsonForMarkers(markers.toList())),
return style!.addSource(
GeoJsonSource(
id: MapUtils.defaultSourceId,
data: jsonEncode(MapUtils.generateGeoJsonForMarkers(markers.toList())),
),
);
}
@@ -27,63 +27,28 @@ extension MapMarkers on MapLibreMapController {
// !! Make sure to remove layers before sources else the native
// maplibre library would crash when removing the source saying that
// the source is still in use
final existingLayers = await getLayerIds();
if (existingLayers.contains(MapUtils.defaultHeatMapLayerId)) {
await removeLayer(MapUtils.defaultHeatMapLayerId);
try {
await style!.removeLayer(MapUtils.defaultHeatMapLayerId);
} catch (_) {
// Layer may not exist
}
final existingSources = await getSourceIds();
if (existingSources.contains(MapUtils.defaultSourceId)) {
await removeSource(MapUtils.defaultSourceId);
try {
await style!.removeSource(MapUtils.defaultSourceId);
} catch (_) {
// Source may not exist
}
await addGeoJSONSourceForMarkers(markers);
if (Platform.isAndroid) {
await addCircleLayer(
MapUtils.defaultSourceId,
MapUtils.defaultHeatMapLayerId,
const CircleLayerProperties(
circleRadius: 10,
circleColor: "rgba(150,86,34,0.7)",
circleBlur: 1.0,
circleOpacity: 0.7,
circleStrokeWidth: 0.1,
circleStrokeColor: "rgba(203,46,19,0.5)",
circleStrokeOpacity: 0.7,
),
);
}
if (Platform.isIOS) {
await addHeatmapLayer(
MapUtils.defaultSourceId,
MapUtils.defaultHeatMapLayerId,
MapUtils.defaultHeatMapLayerProperties,
);
}
await style!.addLayer(
const HeatmapStyleLayer(
id: MapUtils.defaultHeatMapLayerId,
sourceId: MapUtils.defaultSourceId,
paint: MapUtils.defaultHeatMapLayerPaint,
),
);
_completer.complete();
}
Future<Symbol?> addMarkerAtLatLng(LatLng centre) async {
// no marker is displayed if asset-path is incorrect
try {
final ByteData bytes = await rootBundle.load("assets/location-pin.png");
await addImage("mapMarker", bytes.buffer.asUint8List());
return addSymbol(SymbolOptions(geometry: centre, iconImage: "mapMarker", iconSize: 0.15, iconAnchor: "bottom"));
} finally {
// no-op
}
}
Future<LatLngBounds> getBoundsFromPoint(Point<double> point, double distance) async {
final southWestPx = Point(point.x - distance, point.y + distance);
final northEastPx = Point(point.x + distance, point.y - distance);
final southWest = await toLatLng(southWestPx);
final northEast = await toLatLng(northEastPx);
return LatLngBounds(southwest: southWest, northeast: northEast);
}
}