Skip to content

Commit

Permalink
feat: update geojson data (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
josxha authored Sep 23, 2024
1 parent e405d43 commit d6920d1
Show file tree
Hide file tree
Showing 19 changed files with 367 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,20 @@ class MapLibreMapController(
callback(Result.success(Unit))
}

override fun updateGeoJsonSource(
id: String,
data: String,
callback: (Result<Unit>) -> Unit
) {
val source = mapLibreMap.style?.getSourceAs<GeoJsonSource>(id)
if (source == null) {
callback(Result.failure(Exception("LAYER_NOT_FOUND")))
return
}
source.setGeoJson(data)
callback(Result.success(Unit))
}

override fun addImageSource(
id: String,
url: String,
Expand Down
22 changes: 22 additions & 0 deletions android/src/main/kotlin/com/github/josxha/maplibre/Pigeon.g.kt
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,8 @@ interface MapLibreHostApi {
fun removeImage(id: String, callback: (Result<Unit>) -> Unit)
/** Add a GeoJSON source to the map style. */
fun addGeoJsonSource(id: String, data: String, callback: (Result<Unit>) -> Unit)
/** Update the data of a GeoJSON source. */
fun updateGeoJsonSource(id: String, data: String, callback: (Result<Unit>) -> Unit)
/** Add a image source to the map style. */
fun addImageSource(id: String, url: String, coordinates: List<LngLat>, callback: (Result<Unit>) -> Unit)
/** Add a raster source to the map style. */
Expand Down Expand Up @@ -859,6 +861,26 @@ interface MapLibreHostApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.maplibre.MapLibreHostApi.updateGeoJsonSource$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val idArg = args[0] as String
val dataArg = args[1] as String
api.updateGeoJsonSource(idArg, dataArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
reply.reply(wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.maplibre.MapLibreHostApi.addImageSource$separatedMessageChannelSuffix", codec)
if (api != null) {
Expand Down
87 changes: 87 additions & 0 deletions example/lib/animation_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:maplibre/maplibre.dart';

@immutable
class AnimationPage extends StatefulWidget {
const AnimationPage({super.key});

static const location = '/animation';

@override
State<AnimationPage> createState() => _AnimationPageState();
}

const _sourceId = 'AnimatedPath';

class _AnimationPageState extends State<AnimationPage> {
late final MapController _controller;
Timer? _timer;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Animation')),
body: MapLibreMap(
options: MapOptions(zoom: 14, center: Position(-122.01971, 45.632472)),
onMapCreated: (controller) => _controller = controller,
onStyleLoaded: _onStyleLoaded,
),
);
}

Future<void> _onStyleLoaded() async {
final response = await get(
Uri.parse('https://maplibre.org/maplibre-gl-js/docs/assets/hike.geojson'),
);
final geojsonLine = response.body;
final geojson = FeatureCollection.fromJson(
jsonDecode(geojsonLine) as Map<String, Object?>,
);
final lineString = geojson.features.first.geometry! as LineString;
// TODO: setting the id is currently required as geotypes would set it to null, Feature.id documented at https://datatracker.ietf.org/doc/html/rfc7946#section-3.2
geojson.features.first.id = 1;
final allCoords = lineString.coordinates;

// a LineString on MapLibre Native must have at least 2 Points
lineString.coordinates = allCoords.sublist(0, 2);
await _controller.addSource(
GeoJsonSource(id: _sourceId, data: jsonEncode(geojson.toJson())),
);
await _controller.addLayer(
const LineLayer(
id: 'geojson-line',
sourceId: _sourceId,
paint: {'line-color': '#F00', 'line-width': 3},
),
);

var index = 1;
_timer = Timer.periodic(const Duration(milliseconds: 1), (timer) {
index++;
if (index > allCoords.length - 1) {
debugPrint('line animation completed');
timer.cancel();
return;
}
lineString.coordinates = allCoords.sublist(0, index);
_controller.updateGeoJsonSource(
id: _sourceId,
data: jsonEncode(geojson.toJson()),
);
debugPrint(
'[$index] update line: '
'${allCoords[index].lng}, ${allCoords[index].lat}',
);
});
}

@override
void dispose() {
_timer?.cancel();
super.dispose();
}
}
3 changes: 0 additions & 3 deletions example/lib/layers_fill_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,3 @@ class _LayersFillPageState extends State<LayersFillPage> {
);
}
}

// TODO fill extrusion layer
// TODO line layer
3 changes: 0 additions & 3 deletions example/lib/layers_line_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,3 @@ class _LayersLinePageState extends State<LayersLinePage> {
);
}
}

// TODO fill extrusion layer
// TODO line layer
2 changes: 2 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:maplibre_example/animation_page.dart';
import 'package:maplibre_example/annotations_page.dart';
import 'package:maplibre_example/controller_page.dart';
import 'package:maplibre_example/events_page.dart';
Expand Down Expand Up @@ -33,6 +34,7 @@ class MyApp extends StatelessWidget {
routes: {
MenuPage.location: (context) => const MenuPage(),
KioskPage.location: (context) => const KioskPage(),
AnimationPage.location: (context) => const AnimationPage(),
EventsPage.location: (context) => const EventsPage(),
StyledMapPage.location: (context) => const StyledMapPage(),
LayersSymbolPage.location: (context) => const LayersSymbolPage(),
Expand Down
6 changes: 6 additions & 0 deletions example/lib/menu_page.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:maplibre_example/animation_page.dart';
import 'package:maplibre_example/annotations_page.dart';
import 'package:maplibre_example/controller_page.dart';
import 'package:maplibre_example/events_page.dart';
Expand Down Expand Up @@ -68,6 +69,11 @@ class MenuPage extends StatelessWidget {
iconData: Icons.looks_two,
location: TwoMapsPage.location,
),
ItemCard(
label: 'Animation',
iconData: Icons.animation,
location: AnimationPage.location,
),
],
),
const SliverToBoxAdapter(child: SectionTitle('Map Layers')),
Expand Down
21 changes: 21 additions & 0 deletions ios/Classes/Pigeon.g.swift
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ protocol MapLibreHostApi {
func removeImage(id: String, completion: @escaping (Result<Void, Error>) -> Void)
/// Add a GeoJSON source to the map style.
func addGeoJsonSource(id: String, data: String, completion: @escaping (Result<Void, Error>) -> Void)
/// Update the data of a GeoJSON source.
func updateGeoJsonSource(id: String, data: String, completion: @escaping (Result<Void, Error>) -> Void)
/// Add a image source to the map style.
func addImageSource(id: String, url: String, coordinates: [LngLat], completion: @escaping (Result<Void, Error>) -> Void)
/// Add a raster source to the map style.
Expand Down Expand Up @@ -858,6 +860,25 @@ class MapLibreHostApiSetup {
} else {
addGeoJsonSourceChannel.setMessageHandler(nil)
}
/// Update the data of a GeoJSON source.
let updateGeoJsonSourceChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.maplibre.MapLibreHostApi.updateGeoJsonSource\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
updateGeoJsonSourceChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let idArg = args[0] as! String
let dataArg = args[1] as! String
api.updateGeoJsonSource(id: idArg, data: dataArg) { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
updateGeoJsonSourceChannel.setMessageHandler(nil)
}
/// Add a image source to the map style.
let addImageSourceChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.maplibre.MapLibreHostApi.addImageSource\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
Expand Down
3 changes: 3 additions & 0 deletions lib/src/map_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ abstract interface class MapController {
/// of the layers array and appear visually above all other layers.
Future<void> addLayer(Layer layer, {String? belowLayerId});

/// Update the data of a GeoJSON source.
Future<void> updateGeoJsonSource({required String id, required String data});

/// Removes the layer with the given ID from the map's style.
Future<void> removeLayer(String id);

Expand Down
26 changes: 26 additions & 0 deletions lib/src/native/pigeon.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,32 @@ class MapLibreHostApi {
}
}

/// Update the data of a GeoJSON source.
Future<void> updateGeoJsonSource(
{required String id, required String data}) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.maplibre.MapLibreHostApi.updateGeoJsonSource$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[id, data]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}

/// Add a image source to the map style.
Future<void> addImageSource({
required String id,
Expand Down
7 changes: 7 additions & 0 deletions lib/src/native/widget_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -373,4 +373,11 @@ final class MapLibreMapStateNative extends State<MapLibreMap>

@override
Future<void> removeImage(String id) => _hostApi.removeImage(id);

@override
Future<void> updateGeoJsonSource({
required String id,
required String data,
}) =>
_hostApi.updateGeoJsonSource(id: id, data: data);
}
6 changes: 6 additions & 0 deletions lib/src/web/interop/map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ extension type JsMap._(Camera _) implements Camera {

/// Update the maximum bounding box of the map camera.
external void setMaxBounds(LngLatBounds? maxBounds);

/// Get a Source by its id.
external SourceSpecification getSource(String id);
}

/// Anonymous MapOptions for the MapLibre JavaScript [JsMap].
Expand Down Expand Up @@ -170,6 +173,9 @@ extension type SourceSpecification._(JSObject _) implements JSObject {
required JSAny urls,
required JSAny coordinates,
});

/// Used to update the data of a GeoJSON source.
external void setData(JSAny data);
}

/// The specifications of map layers.
Expand Down
9 changes: 9 additions & 0 deletions lib/src/web/widget_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -527,4 +527,13 @@ final class MapLibreMapStateWeb extends State<MapLibreMap>

@override
Future<void> removeSource(String id) async => _map.removeSource(id);

@override
Future<void> updateGeoJsonSource({
required String id,
required String data,
}) async {
final source = _map.getSource(id);
source.setData(parse(data));
}
}
Loading

0 comments on commit d6920d1

Please sign in to comment.