A flutter_map plugin to display fast geojson by slicing into tiles. Slicing based off https://github.com/mapbox/geojson-vt
Getting Started
See the example main.dart, will create specific examples later.
Main features:
Display GeoJSON by splitting them into tiles (tiles are stored in an Index).
Line simplification on GeoJSON.
Polyline/polygon tap/hit detection code.
Can Display Vector tiles (no mapbox styling works on this or is intended to).
Can cluster markers (basic) based on tiles (no spiderfy features).
How it works
Tiles belong to an Index we create at first.
geoJsonIndex = await geoJSON.createIndex('assets/ids.json', tileSize: tileSize);
This creates an index, precalculates zoom 0-2 for performance (higher zooms are calculated on the fly)
Tile Index Options
{
'maxZoom': 14, // max zoom to preserve detail on
'indexMaxZoom': 5, // max zoom in the tile index
'indexMaxPoints': 100000, // max number of points per tile in the tile index
'tolerance': 3, // simplification tolerance (higher means simpler)
'extent': 4096, // tile extent
'buffer': 64, // tile buffer on each side
'lineMetrics': false, // whether to calculate line metrics
'promoteId': null, // name of a feature property to be promoted to feature.id
'generateId': false, // whether to generate feature ids. Cannot be used with promoteId
'debug': 2 // logging level (0, 1 or 2)
};
GeoJsonWidget options
index: Our index we created earlier.
options: our GeoJSONOptions (see below)
drawClusters: true to erm draw clusters instead of raw features.
drawFeatures: true to erm draw the features
(You probably only want one of these enabled, but you can have both enabled for testing).
featuresHaveSameStyle: true if all geometry has the same color. This may give a performance tweak as we can batch draw calls up
Available GeoJSONOptions are: (polygonFunc and lineStringFunc to be implementated, but styles work)
Function? lineStringFunc;
Function? lineStringStyle;
Function? polygonFunc; // callback for polys
Function? polygonStyle; // styling for polys
Function? pointFunc; // marker/points, draw to canvas
Function? pointWidgetFunc; // marker/points, show a widget
Function? pointStyle; // marker/points styling
Function? overallStyleFunc; // one overall callback for a feature
Function? clusterFunc; // call this if we're displaying a cluster
bool featuresHaveSameStyle; // performance improvement (maybe) if all features are the same
Example Widget
GeoJSONWidget(
drawClusters: true,
drawFeatures: false,
index: geoJsonIndex,
options: GeoJSONOptions(
featuresHaveSameStyle: false,
overallStyleFunc: (TileFeature feature) {
var paint = Paint()
..style = PaintingStyle.stroke
..color = Colors.blue
..strokeWidth = 5
..isAntiAlias = false;
if(feature.type == 3) { // lineString
///paint.style = PaintingStyle.fill;
}
return paint;
},
pointWidgetFunc: (TileFeature feature) {
//return const Text("Point!", style: TextStyle(fontSize: 10));
return const Icon(Icons.airplanemode_on);
},
pointStyle: (TileFeature feature) { return Paint(); },
pointFunc: (TileFeature feature, Canvas canvas) {
if(CustomImages.imageLoaded) {
canvas.drawImage(CustomImages.plane, const Offset(0.0, 0.0), Paint());
}
},
///clusterFunc: () { return Text("Cluster"); },
///lineStringFunc: () { if(CustomImages.imageLoaded) return CustomImages.plane;}
polygonFunc: null,
polygonStyle: (feature) {
var paint = Paint()
..style = PaintingStyle.fill
..color = Colors.red
..strokeWidth = 5
..isAntiAlias = false;
if(feature.tags != null && "${feature.tags['NAME']}_${feature.tags['COUNTY']}" == featureSelected) {
return paint;
}
paint.color = Colors.lightBlueAccent;
return paint;
}
),
Example Vector Tile Widget
VectorTileWidgetStream(size: 256.0, index: vectorTileIndex,
options: const {
'urlTemplate': 'https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/{z}/{x}/{y}.mvt?mapbox://styles/<name>/<key/',
'subdomains': ['a', 'b', 'c']},
),
onTap for polygons
onTap: (tapPosition, point) {
featureSelected = null;
// figure which tile we're on, then grab that tiles features to loop through
// to find which feature the tap was on. Zoom 14 is kinda arbitrary here
var pt = const Epsg3857().latLngToPoint(point, mapController.zoom.floorToDouble());
var x = (pt.x / tileSize).floor();
var y = (pt.y / tileSize).floor();
var tile = geoJsonIndex.getTile(mapController.zoom.floor(), x, y);
if(tile != null) {
for (var feature in tile.features) {
var polygonList = feature.geometry;
if (feature.type != 1) {
if(geoJSON.isGeoPointInPoly(pt, polygonList, size: tileSize)) {
infoText = "${feature.tags['NAME']}, ${feature.tags['NAME']} tapped";
if(feature.tags.containsKey('NAME')) {
featureSelected = "${feature.tags['NAME']}_${feature.tags['COUNTY']}";
}
}
}
}
if(featureSelected != null) {
print("Tapped $infoText $featureSelected");
}
}
setState(() {});
},