Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement SymbolLayer #33

Merged
merged 12 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

### Features

- Add sources and layers to the map programmatically.
- Remove sources and layers from the map programmatically.
- Add `duration` parameter to `flyTo()`.
- `flyTo()` returns after the animation completes or throws an exception if it
has been cancelled.
- Add sources and layers to the map programmatically.

### Bug fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import RasterDemEncoding
import ScreenLocation
import TileScheme
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.PointF
import android.view.View
import android.widget.FrameLayout
Expand Down Expand Up @@ -46,7 +47,9 @@ import org.maplibre.android.style.sources.RasterDemSource
import org.maplibre.android.style.sources.RasterSource
import org.maplibre.android.style.sources.TileSet
import org.maplibre.android.style.sources.VectorSource
import java.io.IOException
import java.net.URI
import java.net.URL
import kotlin.coroutines.cancellation.CancellationException


Expand Down Expand Up @@ -363,6 +366,27 @@ class MapLibreMapController(
callback(Result.success(Unit))
}

override fun loadImage(url: String, callback: (Result<ByteArray>) -> Unit) {
try {
val bytes = URL(url).openConnection().getInputStream().readBytes()
callback(Result.success(bytes))
} catch (e: IOException) {
println(e)
callback(Result.failure(e))
}
}

override fun addImage(id: String, bytes: ByteArray, callback: (Result<Unit>) -> Unit) {
val bitmap = BitmapFactory.decodeStream(bytes.inputStream())
mapLibreMap.style?.addImage(id, bitmap)
callback(Result.success(Unit))
}

override fun removeImage(id: String, callback: (Result<Unit>) -> Unit) {
mapLibreMap.style?.removeImage(id)
callback(Result.success(Unit))
}

override fun getCamera(callback: (Result<MapCamera>) -> Unit) {
val position = mapLibreMap.cameraPosition
val target = mapLibreMap.cameraPosition.target!!
Expand Down
68 changes: 68 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 @@ -357,6 +357,15 @@ interface MapLibreHostApi {
fun removeLayer(id: String, callback: (Result<Unit>) -> Unit)
/** Removes the source with the given ID from the map's style. */
fun removeSource(id: String, callback: (Result<Unit>) -> Unit)
/**
* Loads an image to the map. An image needs to be loaded before it can
* get used.
*/
fun loadImage(url: String, callback: (Result<ByteArray>) -> Unit)
/** Add an image to the map. */
fun addImage(id: String, bytes: ByteArray, callback: (Result<Unit>) -> Unit)
/** Removes an image from the map */
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)
/** Add a image source to the map style. */
Expand Down Expand Up @@ -749,6 +758,65 @@ interface MapLibreHostApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.maplibre.MapLibreHostApi.loadImage$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val urlArg = args[0] as String
api.loadImage(urlArg) { result: Result<ByteArray> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.maplibre.MapLibreHostApi.addImage$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val idArg = args[0] as String
val bytesArg = args[1] as ByteArray
api.addImage(idArg, bytesArg) { 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.removeImage$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val idArg = args[0] as String
api.removeImage(idArg) { 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.addGeoJsonSource$separatedMessageChannelSuffix", codec)
if (api != null) {
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/features/_category_.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"position": 4,
"link": {
"type": "generated-index",
"description": "Dive in to the features MapLibre GL for Flutter provides."
"description": "Dive in to the features MapLibre for Flutter provides."
}
}
31 changes: 20 additions & 11 deletions docs/docs/features/supported-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ sidebar_position: 1

# Supported Features

This side provides a broad orientation about what functionality could and what
This side provides a broad orientation about what functionality could and what
functionality is already added.

### Legend
Expand All @@ -26,15 +26,23 @@ lack of platform views of these platforms.

### General Functionality

| Feature | web | android | iOS | windows | macOS | linux |
|---------------------|-----|---------|-----|---------|-------|-------|
| Map | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| MapController | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| UI Controls for web | ✅ | ➖ | ➖ | ➖ | ➖ | ➖ |
| Offline | ➖ | ❌ | ❌ | ➖ | ➖ | ➖ |
| Events | ❌ | ❌ | ❌ | ➖ | ➖ | ➖ |
| Snapshotter | ❌ | ❌ | ❌ | ➖ | ➖ | ➖ |
| Annotations | ❌ | ❌ | ❌ | ➖ | ➖ | ➖ |
| Feature | web | android | iOS | windows | macOS | linux |
|----------------------|-----|---------|-----|---------|-------|-------|
| Map | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| MapController | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| UI Controls for web | ✅ | ➖ | ➖ | ➖ | ➖ | ➖ |
| Offline | ➖ | ❌ | ❌ | ➖ | ➖ | ➖ |
| Events | ❌ | ❌ | ❌ | ➖ | ➖ | ➖ |
| Snapshotter | ➖ | ❌ | ❌ | ➖ | ➖ | ➖ |
| Annotations | ❌ | ❌ | ❌ | ➖ | ➖ | ➖ |
| Circle Layer | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| Fill Layer | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| Fill Extrusion Layer | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| Heatmap Layer | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| Hillshade Layer | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| Line Layer | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| Raster Layer | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| Symbol Layer | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |

### Gestures and other Callbacks

Expand All @@ -55,7 +63,8 @@ lack of platform views of these platforms.
| flyTo | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| addSource | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| addLayer | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| setMyLocationTrackingMode | ❌ | ❌ | ❌ | ➖ | ➖ | ➖ |
| showUserLocation | ❌ | ❌ | ❌ | ➖ | ➖ | ➖ |
| trackUserLocation | ❌ | ❌ | ❌ | ➖ | ➖ | ➖ |
| setMapLanguage | ❌ | ❌ | ❌ | ➖ | ➖ | ➖ |
| toScreenLocation | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
| toLatLng | ✅ | ✅ | ❌ | ➖ | ➖ | ➖ |
Expand Down
25 changes: 25 additions & 0 deletions docs/docs/getting-started/add-dependency.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,28 @@ You can find the latest version of the package on
[pub.dev](https://pub.dev/packages/maplibre) or here:

[![Pub Version](https://img.shields.io/pub/v/maplibre)](https://pub.dev/packages/maplibre)

## Using the development version

If you want to have access to the latest features and changes, you
can use the package directly from GitHub.

:::warning

Note, that the development version shouldn't be used in production systems and
is considered less stable.

:::

This is how you include the package directly from GitHub. Either use it in your
`dependecies:` or temporarily override it inside the `dependency_overrides:`
list.

```yaml title="pubspec.yaml"
dependencies:
# highlight-next-line
maplibre:
git:
url: https://github.com/josxha/flutter-maplibre
ref: main # or a specific commit hash
```
112 changes: 112 additions & 0 deletions example/lib/layers_symbol_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:maplibre/maplibre.dart';

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

static const location = '/layers/symbol';
static const imageUrl =
'https://upload.wikimedia.org/wikipedia/commons/f/f2/678111-map-marker-512.png';

@override
State<LayersSymbolPage> createState() => _LayersSymbolPageState();
}

class _LayersSymbolPageState extends State<LayersSymbolPage> {
late final MapController _controller;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Symbol Layer')),
body: MapLibreMap(
options: MapOptions(zoom: 3, center: Position(9.17, 47.68)),
onMapCreated: (controller) => _controller = controller,
onStyleLoaded: () async {
// load the image data
final response = await http.get(Uri.parse(LayersSymbolPage.imageUrl));
final bytes = response.bodyBytes;

// add the image to the map
await _controller.addImage('marker', bytes);

// add some points as GeoJSON source to the map
await _controller.addSource(
const GeoJsonSource(
id: 'points',
data: _geoJsonString,
),
);

// display the image on the map
await _controller.addLayer(
const SymbolLayer(
id: 'images',
sourceId: 'points',
layout: {
// see https://maplibre.org/maplibre-style-spec/layers/#symbol
'icon-image': 'marker',
'icon-size': 0.08,
'icon-allow-overlap': true,
'icon-anchor': 'bottom',
},
),
);
},
),
);
}
}

const _geoJsonString = '''
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
8.532443386754125,
47.381666653596795
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
-0.11027386926130589,
51.5498511494217
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
13.352826080643723,
52.57278382732386
],
"type": "Point"
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"coordinates": [
12.483543026945483,
41.94075185567311
],
"type": "Point"
}
}
]
}''';
2 changes: 2 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:maplibre_example/layers_heatmap_page.dart';
import 'package:maplibre_example/layers_hillshade_page.dart';
import 'package:maplibre_example/layers_line_page.dart';
import 'package:maplibre_example/layers_raster_page.dart';
import 'package:maplibre_example/layers_symbol_page.dart';
import 'package:maplibre_example/menu_page.dart';
import 'package:maplibre_example/styled_map_page.dart';
import 'package:maplibre_example/two_maps_page.dart';
Expand All @@ -32,6 +33,7 @@ class MyApp extends StatelessWidget {
MenuPage.location: (context) => const MenuPage(),
KioskPage.location: (context) => const KioskPage(),
StyledMapPage.location: (context) => const StyledMapPage(),
LayersSymbolPage.location: (context) => const LayersSymbolPage(),
LayersCirclePage.location: (context) => const LayersCirclePage(),
LayersHeatmapPage.location: (context) => const LayersHeatmapPage(),
LayersHillshadePage.location: (context) => const LayersHillshadePage(),
Expand Down
6 changes: 6 additions & 0 deletions example/lib/menu_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:maplibre_example/layers_heatmap_page.dart';
import 'package:maplibre_example/layers_hillshade_page.dart';
import 'package:maplibre_example/layers_line_page.dart';
import 'package:maplibre_example/layers_raster_page.dart';
import 'package:maplibre_example/layers_symbol_page.dart';
import 'package:maplibre_example/styled_map_page.dart';
import 'package:maplibre_example/two_maps_page.dart';
import 'package:maplibre_example/web_controls_page.dart';
Expand Down Expand Up @@ -103,6 +104,11 @@ class MenuPage extends StatelessWidget {
iconData: Icons.grid_on,
location: LayersRasterPage.location,
),
ItemCard(
label: 'Symbol',
iconData: Icons.emoji_emotions,
location: LayersSymbolPage.location,
),
],
),
],
Expand Down
1 change: 1 addition & 0 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies:
sdk: flutter
geotypes: ^0.0.2
go_router: ^14.2.2
http: ^1.2.2
maplibre:
path: ../

Expand Down
Loading