1
0
Fork 0
mirror of https://gitlab.com/nkming2/nc-photos.git synced 2025-03-27 01:14:43 +01:00
nc-photos/np_gps_map/lib/src/interactive_map/osm.dart

141 lines
4.3 KiB
Dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart';
import 'package:latlong2/latlong.dart';
import 'package:np_common/object_util.dart';
import 'package:np_gps_map/src/interactive_map.dart';
import 'package:np_gps_map/src/map_coord.dart';
typedef OsmClusterBuilder = Widget Function(
BuildContext context, List<DataPoint> dataPoints);
class OsmInteractiveMap extends StatefulWidget {
const OsmInteractiveMap({
super.key,
this.initialPosition,
this.initialZoom,
this.dataPoints,
this.clusterBuilder,
this.onClusterTap,
this.contentPadding,
this.onMapCreated,
});
@override
State<StatefulWidget> createState() => _OsmInteractiveMapState();
final MapCoord? initialPosition;
final double? initialZoom;
final List<DataPoint>? dataPoints;
final OsmClusterBuilder? clusterBuilder;
final void Function(List<DataPoint> dataPoints)? onClusterTap;
final EdgeInsets? contentPadding;
final void Function(InteractiveMapController controller)? onMapCreated;
}
class _OsmInteractiveMapState extends State<OsmInteractiveMap> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_parentController == null) {
_parentController = _ParentController(_controller);
widget.onMapCreated?.call(_parentController!);
}
});
}
@override
Widget build(BuildContext context) {
return FlutterMap(
mapController: _controller,
options: MapOptions(
initialCenter: widget.initialPosition?.toLatLng() ?? const LatLng(0, 0),
initialZoom: max(2.5, widget.initialZoom ?? 2.5),
minZoom: 2.5,
),
children: [
TileLayer(
urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
),
if (widget.dataPoints != null)
MarkerClusterLayerWidget(
options: MarkerClusterLayerOptions(
markers: widget.dataPoints!
.map((e) => _OsmDataPoint(
original: e,
child: _buildMarker(context, [e]),
))
.toList(),
builder: (context, markers) => _buildMarker(
context,
markers.cast<_OsmDataPoint>().map((e) => e.original).toList(),
),
// need to be large enough to contain markers of all size
size: const Size.square(_markerBoundingBoxSize),
// disable all tap handlers from package
zoomToBoundsOnClick: false,
centerMarkerOnClick: false,
spiderfyCluster: false,
markerChildBehavior: true,
),
),
Padding(
padding: widget.contentPadding ?? EdgeInsets.zero,
child: const SimpleAttributionWidget(
source: Text("OpenStreetMap contributors"),
),
),
],
);
}
Widget _buildMarker(BuildContext context, List<DataPoint> dataPoints) {
if (widget.clusterBuilder == null) {
return const SizedBox.shrink();
} else {
return GestureDetector(
onTap: widget.onClusterTap?.let((l) => () => l(dataPoints)),
child: widget.clusterBuilder!(context, dataPoints),
);
}
}
_ParentController? _parentController;
late final _controller = MapController();
}
class _OsmDataPoint extends Marker {
_OsmDataPoint({
required this.original,
required super.child,
}) : super(
point: original.position.toLatLng(),
width: _markerBoundingBoxSize,
height: _markerBoundingBoxSize,
);
final DataPoint original;
}
class _ParentController implements InteractiveMapController {
const _ParentController(this.controller);
@override
void setPosition(MapCoord position) {
controller.move(position.toLatLng(), 10);
}
final MapController controller;
}
extension on MapCoord {
LatLng toLatLng() => LatLng(latitude, longitude);
}
// define the size of each marker's bounding box. This is NOT necessarily the
// size of the marker, it's merely the size of its bounding box and the actual
// size of the content is determined by the child (e.g., the Container widget)
const _markerBoundingBoxSize = 120.0;