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

MapLibre Native Compose Multiplatform Library #2638

Open
louwers opened this issue Jul 21, 2024 · 34 comments
Open

MapLibre Native Compose Multiplatform Library #2638

louwers opened this issue Jul 21, 2024 · 34 comments

Comments

@louwers
Copy link
Collaborator

louwers commented Jul 21, 2024

StreetComplete is looking to implement an iOS version of the app. The largest blocker is that there is no Compose Multiplatform library that uses MapLibre Native. If you are also interested in this, please reach out to @westnordost, maybe some kind of collaboration to work on this is possible.

Jetbrains has a template available here: https://github.com/KevinnZou/compose-multiplatform-library-template/tree/main

An example Compose Multiplatform library is WebView for JetBrains Compose Multiplatform

It uses Native C Interop: https://kotlinlang.org/docs/native-c-interop.html#simple-example with cinterop.

Related PR: #1254

@tarkvara
Copy link
Collaborator

For the record, we were able to get MapLibre working in our KMP app (both Android and iOS). It was however a home-grown solution, and there were some rough edges.

@louwers
Copy link
Collaborator Author

louwers commented Jul 21, 2024

@tarkvara Can you share more details?

@tarkvara
Copy link
Collaborator

I wasn't ambitious enough to create a KMP library, so Android and iOS directly refer to the native libraries. Perhaps somebody with a better understanding of the KMP build tools can wrap them together into a single KMP library.

For Android, it was enough to add maplibre-android = { group = "org.maplibre.gl", name = "android-sdk", version = "11.0.1" } and the corresponding dependency under androidMain.dependencies.

For iOS, I added

        with (iosTarget.compilations) {
            getByName("main") {
                cinterops {
                    create("MapLibre") {
                        packageName("org.maplibre")
                        compilerOpts(
                            "-framework",
                            "MapboxLibre",
                            "-Fsrc/iosFrameworks/MapLibre.xcframework/$subDir"
                        )
                    }
                }
            }
        }
        iosTarget.binaries.all {
            // Tell the linker where the framework is located.
            linkerOpts(
                "-framework",
                "MapLibre",
                "-F../composeApp/src/iosFrameworks/MapLibre.xcframework/$subDir"
            )
        }

Which unfortunately isn't quite correct; the Gradle build doesn't correctly link, so I had to build the app from within Xcode.

As I said, it seems to work for our app, but I wouldn't recommend it as a general solution.

@amirhammad
Copy link

Hi! I just posted this today this in Slack and then found this issue.

Hello! I'm just wondering if there is anybody already working on maplibre in compose multiplatform. I think it is the missing piece in compose multiplatform app development - maps.
I have already started creating a compose multiplatform library that uses maplibre native(ios and android) under the hood. I reworked the expressions to kotlin and the first tests look promising: loading styles, updating camera, adding layers and sources programatically.
But maybe before going any further I just wanted to check if there is already a project doing this exact same thing.
Is there?
Thanks 🙂

I was thinking of creating a proper opensource library for it, but I wanted to make sure it didn't exist yet. But seeing this issue, gives me enough motivation to continue.

@westnordost
Copy link
Collaborator

westnordost commented Jul 22, 2024

Thank you for posting this ticket, @louwers !

@amirhammad Wow, that's cool! There exists in fact a Compose library using MapLibre, Ramani-Maps. But it is for Jetpack Compose and Android-only. Additionally, I am not sure if the API of this library has been designed to support the whole feature set of MapLibre in mind. (see ramani-maps/ramani-maps#77 which I just opened)
At the very least, it can serve as a base or inspiration how the API could look like and how to wire together MapLibre with Compose in general. (But I just realized that I didn't read your cited message to the end - you already did that)

I am also highly interested in this, though I just started using Compose a few months ago, so I don't feel confident to help implementing a Compose library and design its API just yet.
In terms of designing a perfect compose API, it might also be worth looking at and learning from the Google's Maps Compose library.

@JonasVautherin
Copy link
Collaborator

Hej! Co-author of Ramani-Maps here. We would love it if Ramani-Maps worked with Compose Multiplatform! Back when we started, I quickly tried to get Maplibre to run on Compose Multiplatform on my Linux system ("natively", i.e. not with a WebView and Javascript) and my conclusion was that it would take some work there.

If you manage to make Ramani-Maps work "natively" on iOS or Desktop, I would be willing to have a look and help bring that upstream. I am however not very open to solving it with a WebView and Javascript 🙈.

Additionally, I am not sure if the API of this library has been designed to support the whole feature set of MapLibre in mind.

Completely open to contributions! And of course if you feel like you will be better off on your own fork, feel free (it's MPLv2-licensed).

@ianthetechie
Copy link
Collaborator

ianthetechie commented Jul 22, 2024

Looping in @Archdoog here. We currently have a fork of Ramani that's a bit more active (would love to upstream but it's been a bit tough getting responses, and Ramani is currently a good bit broader with Mapbox support, drawing, and a lot of things besides just a core composable library).

We are not presently targeting compose multi platform but aren't opposed to it :)

Does anyone on the thread know how different or related Jetpack Compose and Compose Multiplatform are? EDIT: It looks like the JetBrains README has a helpful excerpt. The devil lies in the details, but this looks promising:

Compose Multiplatform shares most of its API with Jetpack Compose, the Android UI framework developed by Google. You can use the same APIs to build user interfaces for both Android and iOS.

Assuming they are similar, I think it would be a shame to have too many diverging approaches, since we are all going to run into most of the same challenges. Jacob and I have put a lot of work into getting camera stuff correct, as this is a HUGE pain in every similar UI framework (same on iOS, where we are developing a similar wrapper for SwiftUI).

@JonasVautherin
Copy link
Collaborator

would love to upstream but it's been a bit tough getting responses

@ianthetechie: have I missed questions somewhere? 😕

Also note that we have dropped Mapbox support (because Mapbox doesn't play nice with us).

@Archdoog
Copy link
Collaborator

Hey @JonasVautherin! I think @ianthetechie had some initial chats with you on the maplibre slack channel. We tried reaching out there a handful of times.

As for a path forward, I'd suggest we set up a call to give you an introduction to where we've gone from forking ramani to where https://github.com/Rallista/maplibre-compose-playground sits now. There are a few key differences that we could chat about.

@louwers
Copy link
Collaborator Author

louwers commented Jul 26, 2024

You are always welcome to put this on the agenda for the MapLibre Native TSC Meeting as well.

@JonasVautherin
Copy link
Collaborator

JonasVautherin commented Jul 28, 2024

We tried reaching out there a handful of times.

I see. I have not been active on Slack there for a while indeed. The best way to upstream changes is probably to open an issue/PR on the git repository (i.e. the MapLibre Slack is not the official way to reach Ramani-Maps).

We can definitely have a call, I would be happy to hear your thoughts. However I probably won't bring your fork upstream myself. I am happy to discuss, review and merge contributions (on a best-effort basis), but someone would have to make those contributions 😊. If that isn't possible, then as I said you are welcome to keep working on your fork (as long as you honour the licence) 👍.

This said, I wouldn't say Ramani is not active. I believe I have been pretty responsive regarding issues and PRs on the repo. To be honest, I find it a bit unfair to say it has been "tough getting responses".

@westnordost
Copy link
Collaborator

Does anyone on the thread know how different or related Jetpack Compose and Compose Multiplatform are?

Compose Multiplatform is a soft fork of Jetpack Compose, for example, the package names are the same. So theoretically, one could switch out the dependency and be ready for multiplatform. Furthermore, some Jetpack compose APIs may not be available on multiplatform, i.e. they cannot be used or need to have an implementation for every platform. For example stringResource(stringResId: Long), painterResource(drawableResId: Long) etc. are only available on the Android platform (as they access Android resources).

Otherwise, so that the library itself is multiplatform and not only uses a multiplatform library as a dependency, it itself needs to be set up like one, i.e. with the directly structure like src/commonMain, src/androidMain etc. and the correct gradle configuration.

I recently migrated a smallish Java library to a Kotlin Multiplatform library (nothing with Compose though). Maybe this can help:

Jacob and I have put a lot of work into getting camera stuff correct, as this is a HUGE pain in every similar UI framework (same on iOS, where we are developing a similar wrapper for SwiftUI).

Interesting, I would think that the easiest approach would be to ... uh ... basically don't worry about camera animations at all but leave that up to Compose. I.e. just expose the camera position on the API and users would just use the usual Compose stuff for animations. Or is even that a PITA? (I am somewhat of a newbie with Compose, so I only have a very rough idea how that would look like.)

@westnordost
Copy link
Collaborator

Anyway, there is talk about a call - I'd like to join too. Did someone mention a date or a doodle for date-finding? If not, @ianthetechie would you mind setting this up? You two seem to be the ones that advanced the most from what I read here.

@ydrea
Copy link

ydrea commented Jul 30, 2024

+1 on the call initiative!

@SebastianAigner
Copy link

Hi! Seb from JetBrains / Compose Multiplatform here. Would be awesome to have MapLibre available for Compose Multiplatform apps on iOS and Android (or even beyond!) – Let us know if there is something we can help with or if there's any questions that come up during your discussions – happy to connect!

@ianthetechie
Copy link
Collaborator

I've created a Doodle here so we can all get on a call for an hour to discuss :) https://doodle.com/meeting/participate/id/b44RqJ7b

See you soon!

@boldtrn
Copy link
Collaborator

boldtrn commented Aug 18, 2024

Thanks for organising this. I'd be interested in joining too, if we can find a good slot 😺

@westnordost
Copy link
Collaborator

westnordost commented Sep 5, 2024

By the way, there is another. When one searches for MapLibre Compose on DuckDuckGo, one will find this: https://github.com/dellisd/maplibre-compose

It's a bit older, but just yesterday, the guy added new commits.

So, to summarize, there are now:

@dellisd
Copy link

dellisd commented Sep 9, 2024

Thanks for the mention!

I don't have any concrete plans to continue working on maplibre-compose at the moment, but I may revisit it again in the future depending on the needs of my side projects.

Some other prior art I can throw into this thread which may be of some use:

  • mmapp – A number of years ago I worked on some Kotlin Multiplatform bindings targeting Android, iOS, and Web that targeted the data layer of the Mapbox API. It's no longer active, but it might be a helpful reference.
  • compose-web-mapbox – This is an integration of mapbox-gl-js with Compose HTML. A bit less relevant here, but it's where I initially proved out the API design choices I later also used in maplibre-compose and I'm quite happy with the APIs that came out of it!

@michalgwo
Copy link

There is also a library in progress by @skamirmaps, which he described on his blog https://amir.sk/32/compose-map-intro/

@westnordost
Copy link
Collaborator

westnordost commented Oct 4, 2024

Yes, that's the "ComposeMap" I mentioned above. But as long as the source is not available, TBH it's not really interesting because one cannot really talk about it on a technical level.

@amirhammad

This comment was marked as outdated.

@louwers
Copy link
Collaborator Author

louwers commented Oct 11, 2024

Hey @amirhammad thanks for giving an update on your progress! Proprietary software is out of scope of the MapLibre project and advertising/discussing proprietary software should happen on other channels. Of course if you run into any challenges while integrating MapLibre Native that would be in scope in a separate issue, we support all users/integrators of MapLibre Native here.

This discussion is intended to bring people together to discuss and develop an open source Compose Multiplatform library for MapLibre. Thanks for understanding.

@sargunv
Copy link
Collaborator

sargunv commented Nov 2, 2024

I'm working on a Kotlin Multiplatform app using MapLibre, and have a prototype of a Multiplatform wrapper for MapLibre Native. Currently my prototype supports:

  • Instantiating a map under Compose on both iOS and Android
  • Toggling basic UI settings (logo, attribution, gestures)
  • Passing a style via URL
  • Adding a GeoJSON source via URL
  • URLs above work with resource URLs from Compose Multiplatform resources, so you can load assets embedded in the app
  • Adding a line layer with basic configuration (cap, join, color, width)

Currently the code is under a package in my app (https://github.com/sargunv/train-tracker-app), but as it develops and I nail down patterns for a representative subset of features, I intend to split it off into its own library. Feel free to check out the code here: https://github.com/sargunv/train-tracker-app/tree/main/maplibre-compose

Some immediate TODOs I have are:

  • support for expressions when configuring layers
  • support for loading features generated programmatically rather than a GeoJSON URL
  • support for some camera control
  • support for some callbacks (like touch events)
  • testing and stabilizing reloading the style when these inputs change (it seems to just work already, but I haven't stress tested it)
  • determine what inputs to the map should be within a state object, vs regular props, vs content lambda (gotta read guidelines here)
  • figuring out a reasonable way to write ios/android tests for the library

Once some API patterns are nailed down for those TODOs, it's mostly a matter of following those patterns to add support for all the various layer/source configuration (which I'd probably rely on PRs for, beyond my own use cases)

@JonasVautherin
Copy link
Collaborator

Nice work @sargunv!

Out of curiosity: do I understand correctly that this is a wrapper around MapLibre-iOS (what I see here) and MapLibre-Android (see here)?

I guess I am a little confused because you mention that it is a "Multiplatform wrapper for MapLibre Native". But it feels like it is wrapping the Android/iOS SDKs, not the MapLibre Native (the C++ library), right? Or am I missing something?

@sargunv
Copy link
Collaborator

sargunv commented Nov 3, 2024

Ah yup, it's a multiplatform wrapper around the android/iOS sdks (which I thought are part of the MapLibre Native project but I might have my terminology mixed up)

Not fundamentally opposed to interfacing with the actual native library itself; I just chose the route that felt easier to get off the ground

@louwers
Copy link
Collaborator Author

louwers commented Nov 3, 2024

They are both part of the MapLibre Native project! So "Multiplatform wrapper for MapLibre Native" is correct.

Not fundamentally opposed to interfacing with the actual native library itself; I just chose the route that felt easier to get off the ground.

Definitely true. Especially since we don't have a C FFI yet (which seems the way to go if you want to interface with native libraries directly with Kotlin Multiplatform). I think this will be developed over the course of the next year, since it is needed for getting the most performant implementation for Flutter possible, something Toyota has indicated interest in.

But for now building on the Android and iOS SDKs seems to be the way to go for sure. They are widely in use and are not going anywhere.

@JonasVautherin
Copy link
Collaborator

They are both part of the MapLibre Native project! So "Multiplatform wrapper for MapLibre Native" is correct.

Right, my mistake. "Native" is a bit of a strange word: on Android, the "Native Development Kit" (C/C++) is in opposition to the "Software Development Kit" (Java/Kotlin). But then some people use "native" to mean it's using what you find in the official Android documentation (e.g. not a Javascript wrapper). It seems like "MapLibre-Native" is just the name of the project, which again is different.

Especially since we don't have a C FFI

Android can definitely interface with C++ over JNI, and I think iOS can as well through Objective-C++ 🤔. C is probably better for interoperability in general: for instance I am not sure if Go can easily call C++. I am not sure I understand the performance issue with Flutter, though.

From where I stand, what prevents me from using the C++ library with KMP is not the API, but missing implementation. For instance the annotations are implemented in the Android module, not in C++ (if I understand correctly). Or am I missing something?

@louwers
Copy link
Collaborator Author

louwers commented Nov 3, 2024

Android can definitely interface with C++ over JNI, and I think iOS can as well through Objective-C++ 🤔.

This is exactly what we are using for the Android and iOS SDKs. However, you could in theory use Kotlin Multiplatform's Interoperability with C as well. As you mention, you would need to re-implement some of the platform-dependent implementations (e.g. for making HTTP requests). Also the SDKs themselves contain additional functionality, although annotations do exist as a concept in the C++ Core as well.

@JonasVautherin
Copy link
Collaborator

you would need to re-implement some of the platform-dependent implementations (e.g. for making HTTP requests)

But I guess also the rendering engine, right? That felt like the complicated part to me 😇. But maybe that alone justifies having official wrappers for Android/iOS the way they are now, and going for a KMP approach like @sargunv is doing 👍.

@westnordost
Copy link
Collaborator

westnordost commented Nov 3, 2024

FYI the current one and only HTTP library for KMP that works for both Android and iOS is Ktor Client. OkHttp is multiplatform, too, but AFAIK only for JVM and JS.
And for parsing JSON (and thus GeoJson), the go-to solution is Kotlinx.serialization-json, which is also multiplatform for any target.

@louwers
Copy link
Collaborator Author

louwers commented Nov 3, 2024

But I guess also the rendering engine, right?

@JonasVautherin No, the rendering engine is part of the C++ Core.

And for parsing JSON (and thus GeoJson), the go-to solution is Kotlinx.serialization-json, which is also multiplatform for any target.

@westnordost JSON parsing is is all implemented in C++. You wouldn't need to touch it.

FYI the current one and only HTTP library for KMP that works for both Android and iOS is Ktor Client.

You would need some kind of C++ implementation, because the C++ code makes the requests. For example platform/default has a HttpFileSource implementation that uses curl, iOS uses APIs part of Foundation, Android delegates the call to Kotlin land, etc.

Anyway, I don't want to derail the discussion. Just wanted to mention it as a possibility.

@amirhammad
Copy link

After a lot of consideration (maybe too much!) I realised skamirmaps should be open-source.
https://github.com/skamirmaps/skamirmaps
All outside contributions are welcome. Let's come up with APIs that are easy to use!

Thank you

@sargunv
Copy link
Collaborator

sargunv commented Nov 17, 2024

I just want to share that all the TODOs I mentioned in my previous comment are now implemented (except for the one about writing tests).

My experimental Multiplatform wrapper for the Maplibre SDKs now supports:

  • Instantiating a map with a base style URL on iOS and Android
  • Basic UI settings (toggle certain UI controls, etc)
  • Camera control (retrieving camera position as state, setting the position, and animating the position)
  • Expressions
  • Dynamically reloading the style
  • A Composable API for adding sources and layers to the style
    • Including injecting layers below, between, in place of, and above base style layers
  • Basic interactivity (layer composables have callbacks for click and long click, with the clicked feature queried)
  • GeoJSON sources from URL and from Spatial-K geometry
  • Line, fill, circle, and background layers

The code is available in the repo linked in my previous comment above.

Some more immediate TODOs before I feel ready to publish a v0.1 and open up to PRs:

  • Reference base sources in composable layers
  • Configure compass/attribution gravity
  • A couple more layer/source types
  • Some debug logging
  • Basic demo app
  • Some automated tests and CI

Things I'd like to explore soon after v0.1:

  • Transitions
  • Annotations
  • Dynamically modifying the base style
  • JS support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests