maplibre / flutter-maplibre-gl

Customizable, performant and vendor-free vector and raster maps, flutter wrapper for maplibre-native and maplibre-gl-js (fork of flutter-mapbox-gl/maps)
https://pub.dev/packages/maplibre_gl
Other
226 stars 125 forks source link

MapStyleURL when downloading offline region #219

Open noorbakerally opened 1 year ago

noorbakerally commented 1 year ago

When downloading an offline region using downloadOfflineRegion, is it obligatory to supply a style file through mapStyleUrl that is served through HTTP, is it possible to use a file from the assets directory or application document directory?

mariusvn commented 1 year ago

It is not currently possible to do that et the moment. I had this problem too while working on my project and i have a workaround that involve a bit of work.

I start an isolate that contains an HttpServer (see here) that serve the json file.

I only bind the http server to the local loopback

    await HttpServer.bind(InternetAddress.loopbackIPv4, arg.port, shared: true);
    HttpResponse res = (await server.first).response
      ..headers.contentType = arg.contentType
      ..statusCode = 200
      ..write(arg.fileContent);
    await res.close();
    await server.close();

Here, arg.contentType is application/json and arg.fileContent is the json file content.

noorbakerally commented 1 year ago

Is it possible to do that on Android? I'm getting the following error:

SocketException: Failed to create server socket (OS Error: Permission denied, errno = 13), address = 127.0.0.1, port = 80

My codes are:

var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 80, shared: true);
    HttpResponse res = (await server.first).response
      ..headers.contentType = ContentType.json
      ..statusCode = 200
      ..write(styleFileContent);
    await res.close();
    await server.close();
mariusvn commented 1 year ago

you're missing some permission on your manifest.

cf: https://github.com/flutter/flutter/issues/32602#issuecomment-494178777

noorbakerally commented 1 year ago

@mariusvn,

I'm not missing the permission, could something else be causing this?

the fact that the style need to be served via HTTP, is it a requirement of the native libraries also?

mariusvn commented 1 year ago

Can you send me the content of android/app/src/main/AndroidManifest.xml ?

mariusvn commented 1 year ago

@mariusvn,

I'm not missing the permission, could something else be causing this?

the fact that the style need to be served via HTTP, is it a requirement of the native libraries also?

In the original mapbox library, every style, even the main map style was recovered over http in the native library. The download has not yet been updated to get it from a string

mariusvn commented 1 year ago

Hmm weird, it should work with this 🤔

noorbakerally commented 1 year ago

@mariusvn you ever tried this on Android?

mariusvn commented 1 year ago

yeah, I actually use this on my app i am developping right now, the code I sent you is extracted from it

noorbakerally commented 1 year ago

@mariusvn from the codes you provided above, i changed the port to 8080, im no longer getting this problem, but is hanging on the line HttpResponse res = (await server.first).response................, make sense because it is waiting for requests i suppose, so we cannot define this in the order i suppose,

So, im defining it this way:

 HttpServer.bind(InternetAddress.loopbackIPv4, 8080, shared: true).then((server){
      server.first.then((req) async {
        final file = File('assets/styles/style.json');
        final contents = await file.readAsString();
        HttpResponse res = (await server.first).response
          ..headers.contentType = ContentType.json
          ..statusCode = 200
          ..write(contents);
        await res.close();
        await server.close();
      });
    }).whenComplete(() async {
      LatLngBounds latLngBounds = await maplibreMapController.getVisibleRegion();
      var metadata = {
        "date":formatDate(DateTime.now(),[dd, '/', mm, '/', yyyy, ' ',HH, ':',hh])
      };
      await downloadOfflineRegion(
          OfflineRegionDefinition(bounds: latLngBounds,
              mapStyleUrl: "http://127.0.0.1:8080", minZoom: zoomFrom.toDouble(), maxZoom: 15),
          onEvent:downloadNotifier,
          metadata:metadata
      );
    });

The idea is:

  1. The server is defined
  2. A callback to the request handler is defined
  3. downloadOfflineRegion is called with reference to the mapStyleUrl

However, im getting the following error:

D/Mbgl-HttpRequest( 4451): Request failed due to a temporary error: timeout 
D/Mbgl-HttpRequest( 4451): [HTTP] This request was cancelled (http://127.0.0.1:8080/). This is expected for tiles that were being prefetched but are no longer needed for the map to render.
E/OfflineManagerUtils( 4451): onError reason: REASON_SERVER
E/OfflineManagerUtils( 4451): onError message: timeout
mariusvn commented 1 year ago

If you do it with an Http Server, you should create an isolate for him

mariusvn commented 1 year ago

Thats why I'm able to continue to do other things while it waits

mariusvn commented 1 year ago

my code for the server is actually this

class HttpServerService extends Service {

  int _port = 8633;

  int get port {
    return _port;
  }

  set port(int value) {
    if (value < 1 || value > 65536) {
      throw InvalidPortException();
    }
    _port = value;
  }

  Future<void> host(String content, {ContentType? contentType}) async {
    contentType ??= ContentType.json;
    ReceivePort receivePort = ReceivePort();
    await Isolate.spawn(HttpServerService._startServerSingleRequest, _HttpServerArgs(content, contentType, _port, receivePort.sendPort));
    await receivePort.first;
  }

  static void _startServerSingleRequest(_HttpServerArgs arg) async {
    const Logger isolateLogger = Logger('HttpServerServiceIsolate');
    final HttpServer server = await HttpServer.bind(InternetAddress.loopbackIPv4, arg.port, shared: true);
    isolateLogger.info('Server running on port ${arg.port}');
    arg.sendPort.send(1);

    HttpResponse res = (await server.first).response
      ..headers.contentType = arg.contentType
      ..statusCode = 200
      ..write(arg.fileContent);

    await res.close();
    isolateLogger.info('Response done, stopping server ...');
    await server.close();
    isolateLogger.info('Server closed');
    arg.sendPort.send(2);
  }

}

class _HttpServerArgs {

  const _HttpServerArgs(this.fileContent, this.contentType, this.port, this.sendPort);

  final String fileContent;
  final ContentType contentType;
  final int port;
  final SendPort sendPort;

}