Skip to content

Commit 07b4358

Browse files
authored
Added latLngToScreenPoint and refactored pointToLatLng (#1330)
* Added latLngToScreenPoint and refactored pointToLatLng * lint fixes * lint fixes & test import
1 parent 30ca5de commit 07b4358

File tree

5 files changed

+161
-29
lines changed

5 files changed

+161
-29
lines changed

example/lib/main.dart

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:flutter_map_example/pages/epsg4326_crs.dart';
88
import 'package:flutter_map_example/pages/esri.dart';
99
import 'package:flutter_map_example/pages/home.dart';
1010
import 'package:flutter_map_example/pages/interactive_test_page.dart';
11+
import 'package:flutter_map_example/pages/latlng_to_screen_point.dart';
1112
import 'package:flutter_map_example/pages/live_location.dart';
1213
import 'package:flutter_map_example/pages/many_markers.dart';
1314
import 'package:flutter_map_example/pages/map_controller.dart';
@@ -86,6 +87,8 @@ class MyApp extends StatelessWidget {
8687
EPSG3413Page.route: (context) => const EPSG3413Page(),
8788
MaxBoundsPage.route: (context) => const MaxBoundsPage(),
8889
PointToLatLngPage.route: (context) => const PointToLatLngPage(),
90+
LatLngScreenPointTestPage.route: (context) =>
91+
const LatLngScreenPointTestPage(),
8992
},
9093
);
9194
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter/material.dart';
4+
import 'package:flutter_map/flutter_map.dart';
5+
import 'package:flutter_map_example/widgets/drawer.dart';
6+
import 'package:flutter_map/plugin_api.dart';
7+
import 'package:latlong2/latlong.dart';
8+
9+
class LatLngScreenPointTestPage extends StatefulWidget {
10+
static const String route = 'latlng_screen_point_test_page';
11+
12+
const LatLngScreenPointTestPage({Key? key}) : super(key: key);
13+
14+
@override
15+
State createState() {
16+
return _LatLngScreenPointTestPageState();
17+
}
18+
}
19+
20+
class _LatLngScreenPointTestPageState extends State<LatLngScreenPointTestPage> {
21+
late final MapController mapController;
22+
late final StreamSubscription<MapEvent> subscription;
23+
24+
CustomPoint textPos = const CustomPoint(10.0, 10.0);
25+
26+
@override
27+
void initState() {
28+
super.initState();
29+
mapController = MapController();
30+
subscription = mapController.mapEventStream.listen(onMapEvent);
31+
}
32+
33+
@override
34+
void dispose() {
35+
subscription.cancel();
36+
37+
super.dispose();
38+
}
39+
40+
void onMapEvent(MapEvent mapEvent) {
41+
if (mapEvent is! MapEventMove && mapEvent is! MapEventRotate) {
42+
// do not flood console with move and rotate events
43+
debugPrint(mapEvent.toString());
44+
}
45+
}
46+
47+
@override
48+
Widget build(BuildContext context) {
49+
return Scaffold(
50+
appBar: AppBar(title: const Text('LatLng To Screen Point')),
51+
drawer: buildDrawer(context, LatLngScreenPointTestPage.route),
52+
body: Stack(children: [
53+
Padding(
54+
padding: const EdgeInsets.all(8),
55+
child: FlutterMap(
56+
mapController: mapController,
57+
options: MapOptions(
58+
onTap: (tapPos, latLng) {
59+
final pt1 = mapController.latLngToScreenPoint(latLng);
60+
textPos = CustomPoint(pt1!.x, pt1.y);
61+
setState(() {});
62+
},
63+
center: LatLng(51.5, -0.09),
64+
zoom: 11,
65+
rotation: 0,
66+
),
67+
layers: [
68+
TileLayerOptions(
69+
urlTemplate:
70+
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
71+
subdomains: ['a', 'b', 'c'],
72+
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
73+
),
74+
],
75+
),
76+
),
77+
Positioned(
78+
left: textPos.x.toDouble(),
79+
top: textPos.y.toDouble(),
80+
width: 20,
81+
height: 20,
82+
child: const FlutterLogo())
83+
]));
84+
}
85+
}

example/lib/widgets/drawer.dart

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:flutter_map_example/pages/epsg4326_crs.dart';
88
import 'package:flutter_map_example/pages/esri.dart';
99
import 'package:flutter_map_example/pages/home.dart';
1010
import 'package:flutter_map_example/pages/interactive_test_page.dart';
11+
import 'package:flutter_map_example/pages/latlng_to_screen_point.dart';
1112
import 'package:flutter_map_example/pages/live_location.dart';
1213
import 'package:flutter_map_example/pages/many_markers.dart';
1314
import 'package:flutter_map_example/pages/map_controller.dart';
@@ -261,6 +262,8 @@ Drawer buildDrawer(BuildContext context, String currentRoute) {
261262
MapInsideListViewPage.route, currentRoute),
262263
_buildMenuItem(context, const Text('Point to LatLng'),
263264
PointToLatLngPage.route, currentRoute),
265+
_buildMenuItem(context, const Text('LatLng to ScreenPoint'),
266+
LatLngScreenPointTestPage.route, currentRoute),
264267
],
265268
),
266269
);

lib/flutter_map.dart

+2
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ abstract class MapController {
160160

161161
LatLng? pointToLatLng(CustomPoint point);
162162

163+
CustomPoint? latLngToScreenPoint(LatLng latLng);
164+
163165
factory MapController() => MapControllerImpl();
164166
}
165167

lib/src/map/map.dart

+68-29
Original file line numberDiff line numberDiff line change
@@ -81,39 +81,20 @@ class MapControllerImpl implements MapController {
8181
}
8282

8383
@override
84-
LatLng? pointToLatLng(CustomPoint localPoint) {
85-
if (_state.originalSize == null) {
86-
return null;
87-
}
88-
89-
final width = _state.originalSize!.x;
90-
final height = _state.originalSize!.y;
91-
92-
final localPointCenterDistance =
93-
CustomPoint((width / 2) - localPoint.x, (height / 2) - localPoint.y);
94-
final mapCenter =
95-
_state.options.crs.latLngToPoint(_state.center, _state.zoom);
96-
97-
var point = mapCenter - localPointCenterDistance;
98-
99-
if (_state.rotation != 0.0) {
100-
point = rotatePoint(mapCenter, point);
101-
}
84+
CustomPoint latLngToScreenPoint(LatLng latLng) {
85+
return _state.latLngToScreenPoint(latLng);
86+
}
10287

103-
return _state.options.crs.pointToLatLng(point, _state.zoom);
88+
@override
89+
LatLng? pointToLatLng(CustomPoint localPoint) {
90+
return _state.pointToLatLng(localPoint);
10491
}
10592

10693
CustomPoint<num> rotatePoint(
107-
CustomPoint<num> mapCenter, CustomPoint<num> point) {
108-
final m = Matrix4.identity()
109-
..translate(mapCenter.x.toDouble(), mapCenter.y.toDouble())
110-
..rotateZ(-_state.rotationRad)
111-
..translate(-mapCenter.x.toDouble(), -mapCenter.y.toDouble());
112-
113-
final tp = MatrixUtils.transformPoint(
114-
m, Offset(point.x.toDouble(), point.y.toDouble()));
115-
116-
return CustomPoint(tp.dx, tp.dy);
94+
CustomPoint<num> mapCenter, CustomPoint<num> point,
95+
{bool counterRotation = true}) {
96+
return _state.rotatePoint(mapCenter, point,
97+
counterRotation: counterRotation);
11798
}
11899

119100
@override
@@ -648,6 +629,64 @@ class MapState {
648629
return newCenter;
649630
}
650631

632+
// This will convert a latLng to a position that we could use with a widget
633+
// outside of FlutterMap layer space. Eg using a Positioned Widget.
634+
CustomPoint latLngToScreenPoint(LatLng latLng) {
635+
final nonRotatedPixelOrigin =
636+
(project(getCenter(), zoom) - originalSize! / 2.0).round();
637+
638+
var point = options.crs.latLngToPoint(latLng, zoom);
639+
640+
final mapCenter = options.crs.latLngToPoint(center, zoom);
641+
642+
if (rotation != 0.0) {
643+
point = rotatePoint(mapCenter, point, counterRotation: false);
644+
}
645+
646+
return point - nonRotatedPixelOrigin;
647+
}
648+
649+
LatLng? pointToLatLng(CustomPoint localPoint) {
650+
if (originalSize == null) {
651+
return null;
652+
}
653+
654+
final width = originalSize!.x;
655+
final height = originalSize!.y;
656+
657+
final localPointCenterDistance =
658+
CustomPoint((width / 2) - localPoint.x, (height / 2) - localPoint.y);
659+
final mapCenter = options.crs.latLngToPoint(center, zoom);
660+
661+
var point = mapCenter - localPointCenterDistance;
662+
663+
if (rotation != 0.0) {
664+
point = rotatePoint(mapCenter, point);
665+
}
666+
667+
return options.crs.pointToLatLng(point, zoom);
668+
}
669+
670+
// Sometimes we need to make allowances that a rotation already exists, so
671+
// it needs to be reversed (pointToLatLng), and sometimes we want to use
672+
// the same rotation to create a new position (latLngToScreenpoint).
673+
// counterRotation just makes allowances this for this.
674+
CustomPoint<num> rotatePoint(
675+
CustomPoint<num> mapCenter, CustomPoint<num> point,
676+
{bool counterRotation = true}) {
677+
final counterRotationFactor = counterRotation ? -1 : 1;
678+
679+
final m = Matrix4.identity()
680+
..translate(mapCenter.x.toDouble(), mapCenter.y.toDouble())
681+
..rotateZ(rotationRad * counterRotationFactor)
682+
..translate(-mapCenter.x.toDouble(), -mapCenter.y.toDouble());
683+
684+
final tp = MatrixUtils.transformPoint(
685+
m, Offset(point.x.toDouble(), point.y.toDouble()));
686+
687+
return CustomPoint(tp.dx, tp.dy);
688+
}
689+
651690
static MapState? maybeOf(BuildContext context, {bool nullOk = false}) {
652691
final widget =
653692
context.dependOnInheritedWidgetOfExactType<MapStateInheritedWidget>();

0 commit comments

Comments
 (0)