구현 로직 설명 - 위젯 부분
1. 지도 위에 방향 지시자 겹쳐놓기
Stack 위젯 안에 google_maps_flutter에서 제공하는 GoogleMap위젯을 위치시키고 그 위에 방향 지시자 위젯들이 렌더링되도록 한다.
Stack(
children: [
GoogleMap(
...
),
if (widget.isIndicatorVisible && widget.markers.isNotEmpty)
..._indicatorOffsetList.map<Widget>((el) {
return Positioned(
...
);
}).toList(),
],
);
2. GoogleMap 위젯이 받는 파라미터 대신 받기
패키지에서 제공하는 위젯이 구글맵 위젯의 부모 위젯이 되기 때문에 부모 위젯에서 구글맵 위젯이 받는 파라미터를 대신 받도록 한다.
class GoogleMapWithDirectionIndicator extends StatefulWidget {
const GoogleMapWithDirectionIndicator({
super.key,
required this.initialCameraPosition,
this.onMapCreated,
this.gestureRecognizers = const <Factory<OneSequenceGestureRecognizer>>{},
this.webGestureHandling,
... // 구글맵 위젯이 받는 파라미터 대신 받아서 전달하기
// 구글맵 위젯의 파라미터들 외에 추가로 필요한 파라미터
this.height,
this.width,
required this.controller,
MaterialColor indicatorColor = Colors.blue,
this.indicatorSize = const Size(30, 30),
this.isIndicatorVisible = true,
});
final MapCreatedCallback? onMapCreated;
final CameraPosition initialCameraPosition;
...
final Completer<GoogleMapController> controller;
final double? width;
final double? height;
final Size indicatorSize;
final bool isIndicatorVisible;
@override
State<GoogleMapWithDirectionIndicator> createState() =>
_GoogleMapWithDirectionIndicatorState();
}
3. 추가로 필요한 파라미터 작성
구글맵 위젯의 크기나 방향 지시자 위젯의 표시 여부, 크기, 색상, 컨트롤러 등이 필요한 파라미터를 추가로 받도록 작성한다.
this.height,
this.width,
required this.controller,
this.indicatorColor = Colors.blue,
this.indicatorSize = const Size(30, 30),
this.isIndicatorVisible = true,
final double? width;
final double? height;
final Completer<GoogleMapController> controller;
final MaterialColor indicatorColor;
final Size indicatorSize;
final bool isIndicatorVisible;
4. Offset과 angle값을 담는 클래스 생성, 서비스로부터 값 받아오기
구글맵 위경도와 마커 위경도 값을 통해 방향 지시자 위치와 각도를 계산하는 서비스로부터 위치와 각도 값을 가져온다. 편하게 한 객체에 데이터를 담을 수 있도록 클래스 생성
class OffsetWithAngle {
Offset offset;
double angle;
OffsetWithAngle(this.offset, this.angle);
}
서비스로부터 이 데이터 형식으로 값을 받아와 렌더링 요청 (간단하게 setStat 사용)
renderIndicator() async {
List<OffsetWithAngle> offsets = await _service.calculateOffsets(
widget.controller,
widget.markers
.map<LatLng>((marker) =>
LatLng(marker.position.latitude, marker.position.longitude))
.toList(),
widget.indicatorSize);
if (mounted) {
setState(() {
_indicatorOffsetList = offsets;
});
}
}
5. Positioned 위젯과 Translate.rotate 위젯으로 표현
Stack위젯 안에서 구글맵 위에 보이도록 구글맵 위젯 아래에 작성.
if (widget.isIndicatorVisible && widget.markers.isNotEmpty)
..._indicatorOffsetList.map<Widget>((el) {
return Positioned(
left: el.offset.dx,
top: el.offset.dy,
child: IgnorePointer(
ignoring: true,
child: Transform.rotate(
angle: -el.angle * math.pi / 180,
child: Image.asset(
'assets/images/arrow.png',
color: Colors.blue,
height: widget.indicatorSize.height,
width: widget.indicatorSize.width,
),
),
),
);
}).toList(),
구현 로직 설명 - 서비스 부분
1. 지도 경계선의 위/경도와 지도 중심의 위/경도 구하기
GoolgeMapController.getVisibleRegion 메서드를 활용하여 지도 경계선의 위/경도를 구한 다음 (북쪽 위도와 남쪽 위도의 합 / 2), (서쪽 경도와 동쪽 경도의 합 / 2) 를 통해 지도 중심의 위경도 값을 구한다.
Future<List<OffsetWithAngle>> calculateOffsets(
Completer<GoogleMapController> mapController,
List<LatLng> listLatLng,
Size directionIndicatorSize) async {
final GoogleMapController controller = await mapController.future;
LatLngBounds bounds = await controller.getVisibleRegion();
double latRange = bounds.northeast.latitude - bounds.southwest.latitude;
double lngRange = bounds.northeast.longitude - bounds.southwest.longitude;
final LatLng mapCenterLatLng = LatLng(
bounds.southwest.latitude + latRange / 2.0,
bounds.southwest.longitude + lngRange / 2.0);
...
}
2. 마커의 위경도와 비교하여 경계선 밖에 있는 마커 필터링하기
Future<List<OffsetWithAngle>> calculateOffsets(
Completer<GoogleMapController> mapController,
List<LatLng> listLatLng,
Size directionIndicatorSize) async {
...
List<LatLng> resList = listLatLng
.where((latLng) => !(latLng.latitude >= bounds.southwest.latitude &&
latLng.latitude <= bounds.northeast.latitude &&
latLng.longitude >= bounds.southwest.longitude &&
latLng.longitude <= bounds.northeast.longitude))
.toList();
}
3. 위젯의 높이와 너비 : 지도의 위경도 의 비율을 구하고 마커와 지도 중심의 위경도의 각도를 구하여 실제 표시해야 할 각도를 구한다.
Future<List<OffsetWithAngle>> calculateOffsets(
Completer<GoogleMapController> mapController,
List<LatLng> listLatLng,
Size directionIndicatorSize) async {
double latPerPixel = _widgetHeight / latRange;
double lngPerPixel = _widgetWidth / lngRange;
return resList
.map((e) => calculateIndicatorPosition(directionIndicatorSize, e,
mapCenterLatLng, latPerPixel, lngPerPixel))
.toList();
}
3-1. 지도 중심 위경도와 마커 위경도의 각도를 구한다.
지도 중심 위경도와 마커 위경도의 각도를 구한 뒤
위도 경도가 실제 픽셀 크기로 어떤 비율로 그려지는지 실제 위도 당 픽셀, 경도 당 픽셀 비율을 적용해야 정확한 각도를 구할 수 있다.
double calculateAngle(LatLng markerLatLng, LatLng centerLatLng,
double latPerPixel, double lngPerPixel) {
double deltaX =
(markerLatLng.longitude - centerLatLng.longitude) * lngPerPixel;
double deltaY =
(markerLatLng.latitude - centerLatLng.latitude) * latPerPixel;
return math.atan2(deltaY, deltaX) * (180 / math.pi);
}
3-3. 삼각함수를 활용하여 위치를 구한다.
a와 c의 접점을 지도의 중심이라 생각하고 c와 b의 접점을 마커의 위치라고 생각하자. 우리가 가지고 있는 값은 a = 위젯의 너비/2, 그리고 θ의 각도이다. 그리고 구해야할 값은 b값이다. 그러므로 b = tanθ * a 이다.
이제 마커가 몇 분지에 있는지에 따라 b를 다르게 적용하여 offset값을 구한다.
OffsetWithAngle calculateIndicatorPosition(
Size directionIndicatorSize,
LatLng markerLatLng,
LatLng centerLatLng,
double latPerPixel,
double lngPerPixel) {
double angle =
calculateAngle(markerLatLng, centerLatLng, latPerPixel, lngPerPixel);
double centerX = _widgetWidth / 2;
double centerY = _widgetHeight / 2;
double x, y;
if (angle >= -_topRightDiagonalAngle && angle <= _topRightDiagonalAngle) {
x = _widgetWidth - directionIndicatorSize.width;
y = centerY -
centerX * math.tan(angle * math.pi / 180) +
directionIndicatorSize.width / 2 * math.tan(angle * math.pi / 180) -
directionIndicatorSize.height / 2;
} else if (angle >= 180 - _topRightDiagonalAngle ||
angle <= -180 + _topRightDiagonalAngle) {
x = 0;
y = centerY +
centerX * math.tan(angle * math.pi / 180.0) -
directionIndicatorSize.width / 2 * math.tan(angle * math.pi / 180) -
directionIndicatorSize.height / 2;
} else if (angle < 180 - _topRightDiagonalAngle &&
angle > _topRightDiagonalAngle) {
y = 0;
x = centerX -
centerY * math.tan((angle - 90) * math.pi / 180.0) +
directionIndicatorSize.width /
2 *
math.tan((angle - 90) * math.pi / 180) -
directionIndicatorSize.height / 2;
} else {
y = _widgetHeight - directionIndicatorSize.height;
x = centerX +
centerY * math.tan((angle + 90) * math.pi / 180.0) -
directionIndicatorSize.width /
2 *
math.tan((angle + 90) * math.pi / 180.0) -
directionIndicatorSize.height / 2;
}
return OffsetWithAngle(Offset(x, y), angle);
}
'Flutter > Open Source' 카테고리의 다른 글
[Flutter] GoogleMap에 방향 지시자 추가하는 패키지 만들기 - 2 (0) | 2024.01.29 |
---|---|
[Flutter] GoogleMap에 방향 지시자 추가하는 패키지 만들기 - 1 (0) | 2024.01.29 |