Skip to content

Commit cd2f035

Browse files
committed
혼잡도 시각화 개선
혼잡도 시각화 개선
1 parent cbb6004 commit cd2f035

File tree

8 files changed

+1508
-48
lines changed

8 files changed

+1508
-48
lines changed

lib/features/home/screen.dart

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:intl/intl.dart';
66
import 'package:tradule/common/global_value.dart';
77
import 'package:tradule/common/itinerary_card.dart';
88
import 'package:tradule/common/sort_widget.dart';
9+
import 'package:tradule/features/itinerary/congestion.dart';
910
import 'package:tradule/features/itinerary/screen_legacy.dart';
1011
import 'package:tradule/features/my_place/screen.dart';
1112
import 'package:tradule/server_wrapper/server_wrapper.dart';
@@ -488,6 +489,10 @@ class _MainPageState extends State<MainPage>
488489
if (!value) return;
489490
refreshRoute(itineraryCubit.state);
490491
});
492+
fetchCongestionData().then((value) {
493+
if (!value) return;
494+
refreshRoute(itineraryCubit.state);
495+
});
491496
},
492497
onEditPressed: () {
493498
Navigator.push(

lib/features/itinerary/BMEC.dart

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/// ___ _ _ ___ ___
2+
/// |___) | | | |___ |
3+
/// |___) | | |___ |___
4+
///
5+
/// @copyright Copyright (c) 2023 BMEC Technologies. All rights reserved.
6+
7+
import 'dart:math' hide log;
8+
9+
import 'package:google_maps_flutter/google_maps_flutter.dart';
10+
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
11+
import 'package:vector_math/vector_math_64.dart';
12+
13+
/// Conversion factor from pixel size to coordinate degrees.
14+
/// Estimated at [_scalingZoom];
15+
const double _pixelsPerLatLngDeg = 1500000;
16+
17+
/// Map zoom level at which [_pixelsPerLatLngDeg] was estimated.
18+
const double _scalingZoom = 19;
19+
20+
/// Exponent base at which the map scales.
21+
const double _scalingRateBase = 2;
22+
23+
/// Computes the scaling factor based on the [mapZoom].
24+
///
25+
/// The desired [size] is divided by the rate of scaling of the map.
26+
/// See [CameraPosition.zoom] for details.
27+
double computeScale({required int size, required double mapZoom}) =>
28+
size / _pixelsPerLatLngDeg / pow(_scalingRateBase, mapZoom - _scalingZoom);
29+
30+
/// Special instance of [Circle] that allows for scaling on map zoom.
31+
class CircleMarker extends Circle {
32+
/// Size of the marker approximately in pixels.
33+
/// This has not been verified for all devices and/or platforms.
34+
final int size;
35+
36+
/// See [CameraPosition.zoom].
37+
final double mapZoom;
38+
39+
CircleMarker({
40+
required super.circleId,
41+
required this.mapZoom,
42+
required this.size,
43+
super.consumeTapEvents,
44+
super.fillColor,
45+
super.center,
46+
super.strokeColor,
47+
super.strokeWidth,
48+
super.visible,
49+
super.zIndex,
50+
super.onTap,
51+
}) : super(
52+
radius: computeScale(
53+
size: size,
54+
mapZoom: mapZoom,
55+
));
56+
57+
/// Returns a copy redrawn with an updated [mapZoom].
58+
copyWithZoom({required double mapZoom}) => CircleMarker(
59+
circleId: circleId,
60+
mapZoom: mapZoom,
61+
size: size,
62+
consumeTapEvents: consumeTapEvents,
63+
fillColor: fillColor,
64+
center: center,
65+
strokeColor: strokeColor,
66+
strokeWidth: strokeWidth,
67+
visible: visible,
68+
zIndex: zIndex,
69+
onTap: onTap,
70+
);
71+
}
72+
73+
class PolygonMarkerIcon {
74+
/// Unit square icon.
75+
static final List<Vector2> squareIcon = [
76+
Vector2(-0.5, -0.5),
77+
Vector2(-0.5, 0.5),
78+
Vector2(0.5, 0.5),
79+
Vector2(0.5, -0.5),
80+
];
81+
82+
/// Material Design Icon `navigation`.
83+
/// See https://petershaggynoble.github.io/MDI-Sandbox/?icon=navigation.
84+
static final List<Vector2> navigationIcon = [
85+
Vector2(0, -0.5),
86+
Vector2(0.395, 0.463),
87+
Vector2(0.357, 0.500),
88+
Vector2(0.000, 0.342),
89+
Vector2(-0.358, 0.500),
90+
Vector2(-0.395, 0.463),
91+
];
92+
}
93+
94+
/// Special instance of [Polygon] that acts as a [Marker] and allows for scaling
95+
/// and rotation on map zoom. Used due to marker anchor and rotation having no
96+
/// effect on web.
97+
class PolygonMarker extends Polygon {
98+
/// List of (x, y) coordinates that define the vertices of the
99+
/// **unit** polygon. (0, 0) defines the centroid of rotation and scale.
100+
/// FEATURE add support for an anchor.
101+
final List<Vector2> iconPoints;
102+
103+
/// Size of the marker approximately in pixels.
104+
/// This has not been verified for all devices and/or platforms.
105+
final int size;
106+
107+
/// See [CameraPosition.zoom].
108+
final double mapZoom;
109+
110+
/// Geographical location of the marker.
111+
final LatLng position;
112+
113+
/// Rotation of the marker image in degrees clockwise from the [anchor] point.
114+
final double rotation;
115+
116+
/// Pseudo constructor for this mixin.
117+
PolygonMarker({
118+
required this.iconPoints,
119+
required this.size,
120+
required this.mapZoom,
121+
required this.position,
122+
required this.rotation,
123+
required super.polygonId,
124+
super.consumeTapEvents,
125+
super.fillColor,
126+
super.geodesic,
127+
super.strokeColor,
128+
super.strokeWidth,
129+
super.visible,
130+
super.zIndex,
131+
super.onTap,
132+
}) : super(points: []) {
133+
assert(
134+
iconPoints
135+
.expand((iconPoint) => [iconPoint.x.abs(), iconPoint.y.abs()])
136+
.reduce(max) <=
137+
0.5,
138+
"iconPoints should be a unit icon i.e. lie inside a unit square centered at (0,0)");
139+
points
140+
..clear()
141+
..addAll(_transformPoints().map((point) => LatLng(point.y, point.x)));
142+
}
143+
144+
/// Sets [points] to the rotated transform of [_scaledPoints] about
145+
/// [position].
146+
Iterable<Vector2> _transformPoints() =>
147+
iconPoints.map((point) => _transformPoint(
148+
point: point,
149+
position: Vector2(position.longitude, position.latitude),
150+
rotationDegrees: rotation,
151+
scale: computeScale(size: size, mapZoom: mapZoom),
152+
));
153+
154+
/// Returns a copy redrawn with an updated [mapZoom], [position] and/or
155+
/// [rotation]
156+
Polygon copyWithTransform({
157+
double? mapZoom,
158+
LatLng? position,
159+
double? rotation,
160+
}) =>
161+
PolygonMarker(
162+
iconPoints: iconPoints,
163+
size: size,
164+
mapZoom: mapZoom ?? this.mapZoom,
165+
position: position ?? this.position,
166+
rotation: rotation ?? this.rotation,
167+
polygonId: polygonId,
168+
consumeTapEvents: consumeTapEvents,
169+
fillColor: fillColor,
170+
geodesic: geodesic,
171+
strokeColor: strokeColor,
172+
strokeWidth: strokeWidth,
173+
visible: visible,
174+
zIndex: zIndex,
175+
onTap: onTap,
176+
);
177+
178+
/// Rotates a [point] by [rotationDegrees], scales by [scale] and offsets
179+
/// to [position].
180+
static Vector2 _transformPoint({
181+
required Vector2 point,
182+
required Vector2 position,
183+
required double rotationDegrees,
184+
required double scale,
185+
}) =>
186+
(Matrix4.translationValues(position.x, position.y, 0)
187+
..rotateZ(radians((-1 * rotationDegrees) + 180)))
188+
.transform3(Vector3(point.x, point.y, 0).scaled(scale))
189+
.xy;
190+
}
+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:http/http.dart' as http;
3+
import 'package:csv/csv.dart';
4+
import 'dart:convert';
5+
6+
import 'congestion_place.dart';
7+
8+
List<List<dynamic>> globalCongestionCsvData = [];
9+
Future<bool> fetchCongestionData() async {
10+
if (globalCongestionCsvData.isNotEmpty) return true;
11+
final response =
12+
await http.get(Uri.parse('https://congestion-iubedabnlq-du.a.run.app'));
13+
14+
if (response.statusCode == 200) {
15+
final csvBody = response.body;
16+
List<List<dynamic>> csvTable =
17+
CsvToListConverter().convert(csvBody, eol: '\n');
18+
globalCongestionCsvData = csvTable;
19+
return true;
20+
} else {
21+
print('Failed to load congestion data');
22+
}
23+
return false;
24+
}
25+
26+
class CongestionData {
27+
final DateTime time;
28+
final double minCongestion;
29+
final double maxCongestion;
30+
final String level;
31+
final Color fillColor;
32+
final Color strokeColor;
33+
final double latitude;
34+
final double longitude;
35+
36+
CongestionData(
37+
{required this.time,
38+
required this.minCongestion,
39+
required this.maxCongestion,
40+
required this.level,
41+
required this.fillColor,
42+
required this.strokeColor,
43+
required this.latitude,
44+
required this.longitude});
45+
}
46+
47+
List<CongestionData> getAllCongestionData(DateTime time) {
48+
if (globalCongestionCsvData.isEmpty) return [];
49+
List<CongestionData> congestionDataList = [];
50+
int weekday = time.weekday;
51+
int rowIdx = (weekday - 1) * 24 * 12 + time.hour * 12 + time.minute ~/ 5 + 1;
52+
if (rowIdx >= globalCongestionCsvData.length) return [];
53+
var row = globalCongestionCsvData[rowIdx];
54+
var length = globalCongestionPlaceList.length;
55+
for (var i = 0; i < length; i++) {
56+
Map<String, dynamic> place = globalCongestionPlaceList[i];
57+
Color fillColor;
58+
var level = row[i * 3 + 1];
59+
switch (level) {
60+
case '여유':
61+
fillColor = const Color(0xFF9CEFFF).withOpacity(0.3);
62+
break;
63+
case '보통':
64+
fillColor = const Color(0xFFC8FF9C).withOpacity(0.3);
65+
break;
66+
case '약간 붐빔':
67+
fillColor = const Color(0xFFFFD29C).withOpacity(0.3);
68+
break;
69+
case '붐빔':
70+
fillColor = const Color(0xFFFF9C9C).withOpacity(0.3);
71+
break;
72+
default:
73+
fillColor = Colors.grey.withOpacity(0.3);
74+
}
75+
Color strokeColor;
76+
switch (level) {
77+
case '여유':
78+
strokeColor = const Color(0xFF0BB2D1);
79+
break;
80+
case '보통':
81+
strokeColor = const Color(0xFF09AB19);
82+
break;
83+
case '약간 붐빔':
84+
strokeColor = const Color(0xFFFFA63D);
85+
break;
86+
case '붐빔':
87+
strokeColor = const Color(0xFFFF8686);
88+
break;
89+
default:
90+
strokeColor = Colors.grey;
91+
}
92+
congestionDataList.add(CongestionData(
93+
time: time,
94+
minCongestion: row[i * 3 + 2],
95+
maxCongestion: row[i * 3 + 3],
96+
level: level,
97+
fillColor: fillColor,
98+
strokeColor: strokeColor,
99+
latitude: place['latitude'],
100+
longitude: place['longitude']));
101+
}
102+
return congestionDataList;
103+
}

0 commit comments

Comments
 (0)