From fcfb580a4ea942929a73d81e49389b1830cd883a Mon Sep 17 00:00:00 2001 From: Kamiya Date: Thu, 7 Aug 2025 09:14:29 +0800 Subject: [PATCH] feat: map symbol value --- lib/app/map/_lib/managers/precipitation.dart | 139 ++++++++++--------- lib/app/map/_lib/managers/temperature.dart | 69 +++++++-- lib/app/map/_lib/managers/tsunami.dart | 2 +- lib/app/map/_lib/managers/wind.dart | 48 ++++++- lib/app/map/page.dart | 2 +- lib/app_old/page/map/tsunami/tsunami.dart | 2 +- lib/app_old/page/map/weather/humidity.dart | 2 +- lib/app_old/page/map/weather/pressure.dart | 2 +- lib/utils/constants.dart | 14 +- lib/widgets/map/map.dart | 4 +- pubspec.lock | 54 ++----- 11 files changed, 209 insertions(+), 129 deletions(-) diff --git a/lib/app/map/_lib/managers/precipitation.dart b/lib/app/map/_lib/managers/precipitation.dart index 25c2d03c5..ed78af6d7 100644 --- a/lib/app/map/_lib/managers/precipitation.dart +++ b/lib/app/map/_lib/managers/precipitation.dart @@ -13,6 +13,7 @@ import 'package:dpip/app/map/_widgets/map_legend.dart'; import 'package:dpip/core/i18n.dart'; import 'package:dpip/core/providers.dart'; import 'package:dpip/models/data.dart'; +import 'package:dpip/utils/constants.dart'; import 'package:dpip/utils/extensions/build_context.dart'; import 'package:dpip/utils/extensions/latlng.dart'; import 'package:dpip/utils/extensions/string.dart'; @@ -65,7 +66,6 @@ class PrecipitationMapLayerManager extends MapLayerManager { await setup(); onTimeChanged?.call(time); - } catch (e, s) { TalkerManager.instance.error('PrecipitationMapLayerManager.setPrecipitationTime', e, s); } finally { @@ -110,6 +110,8 @@ class PrecipitationMapLayerManager extends MapLayerManager { Future setup() async { if (didSetup) return; + final colors = context.colors; + try { if (GlobalProviders.data.precipitation.isEmpty) { final precipitationList = (await ExpTech().getRainList()).reversed.toList(); @@ -151,75 +153,83 @@ class PrecipitationMapLayerManager extends MapLayerManager { } if (!isLayerExists) { - final properties = { + final Map properties = { for (final interval in precipitationIntervals) - interval: CircleLayerProperties( - circleRadius: [ - Expressions.interpolate, - ['linear'], - [Expressions.zoom], - 7, - 5, - 12, - 15, - ], - circleColor: [ - Expressions.interpolate, - ['linear'], - [Expressions.get, interval], - 0, - '#c2c2c2', - 10, - '#9cfcff', - 30, - '#059bff', - 50, - '#39ff03', - 100, - '#fffb03', - 200, - '#ff9500', - 300, - '#ff0000', - 500, - '#fb00ff', - 1000, - '#960099', - 2000, - '#000000', - ], - circleOpacity: [ - 'case', - [ - '<', + ...({ + interval: CircleLayerProperties( + circleColor: [ + Expressions.interpolate, + ['linear'], [Expressions.get, interval], 0, + '#c2c2c2', + 10, + '#9cfcff', + 30, + '#059bff', + 50, + '#39ff03', + 100, + '#fffb03', + 200, + '#ff9500', + 300, + '#ff0000', + 500, + '#fb00ff', + 1000, + '#960099', + 2000, + '#000000', ], - 0, - 0.7, - ], - circleStrokeWidth: 0.2, - circleStrokeColor: '#000000', - circleStrokeOpacity: [ - 'case', - [ - '<', - [Expressions.get, interval], - 0, + circleRadius: kCircleIconSize, + circleOpacity: 0.75, + circleStrokeColor: colors.outlineVariant.toHexStringRGB(), + circleStrokeWidth: 0.5, + circleStrokeOpacity: 0.75, + visibility: interval == currentPrecipitationInterval.value ? 'visible' : 'none', + ), + '$interval-label': SymbolLayerProperties( + textField: [ + Expressions.concat, + [Expressions.get, 'name'], + '\n', + [ + Expressions.concat, + [Expressions.get, interval], + 'mm', + ], ], - 0, - 0.7, - ], - visibility: interval == currentPrecipitationInterval.value ? 'visible' : 'none', - ), + textSize: 10, + textColor: colors.onSurfaceVariant.toHexStringRGB(), + textHaloColor: colors.outlineVariant.toHexStringRGB(), + textHaloWidth: 1, + textFont: ['Noto Sans TC Bold'], + textOffset: [0, 1], + textAnchor: 'top', + visibility: interval == currentPrecipitationInterval.value ? 'visible' : 'none', + ), + }), }; await Future.wait( - properties.entries.map( - (entry) => controller - .addLayer(sourceId, '$layerId-${entry.key}', entry.value, belowLayerId: BaseMapLayerIds.userLocation) - .then((value) {}), - ), + properties.entries.map((entry) { + final isValueLayer = entry.key.endsWith('-label'); + final interval = isValueLayer ? entry.key.substring(0, entry.key.length - 6) : entry.key; + + return controller.addLayer( + sourceId, + '$layerId-${entry.key}', + entry.value, + belowLayerId: BaseMapLayerIds.userLocation, + minzoom: isValueLayer ? 10 : null, + filter: [ + Expressions.largerOrEqual, + [Expressions.get, interval], + 0, + ], + ); + }), ); } @@ -237,9 +247,11 @@ class PrecipitationMapLayerManager extends MapLayerManager { final layerId = MapLayerIds.precipitation(currentPrecipitationTime.value); final hideLayerId = '$layerId-${currentPrecipitationInterval.value}'; + final hideValueLayerId = '$layerId-${currentPrecipitationInterval.value}-label'; try { await controller.setLayerVisibility(hideLayerId, false); + await controller.setLayerVisibility(hideValueLayerId, false); visible = false; } catch (e, s) { @@ -253,9 +265,11 @@ class PrecipitationMapLayerManager extends MapLayerManager { final layerId = MapLayerIds.precipitation(currentPrecipitationTime.value); final showLayerId = '$layerId-${currentPrecipitationInterval.value}'; + final showValueLayerId = '$layerId-${currentPrecipitationInterval.value}-label'; try { await controller.setLayerVisibility(showLayerId, true); + await controller.setLayerVisibility(showValueLayerId, true); await _focus(); @@ -273,6 +287,7 @@ class PrecipitationMapLayerManager extends MapLayerManager { for (final interval in precipitationIntervals) { await controller.removeLayer('$layerId-$interval'); + await controller.removeLayer('$layerId-$interval-label'); } await controller.removeSource(sourceId); diff --git a/lib/app/map/_lib/managers/temperature.dart b/lib/app/map/_lib/managers/temperature.dart index ed91b9659..1709da6c6 100644 --- a/lib/app/map/_lib/managers/temperature.dart +++ b/lib/app/map/_lib/managers/temperature.dart @@ -14,6 +14,7 @@ import 'package:dpip/core/i18n.dart'; import 'package:dpip/core/providers.dart'; import 'package:dpip/models/data.dart'; import 'package:dpip/models/settings/ui.dart'; +import 'package:dpip/utils/constants.dart'; import 'package:dpip/utils/extensions/build_context.dart'; import 'package:dpip/utils/extensions/int.dart'; import 'package:dpip/utils/extensions/latlng.dart'; @@ -89,6 +90,8 @@ class TemperatureMapLayerManager extends MapLayerManager { Future setup() async { if (didSetup) return; + final colors = context.colors; + try { if (GlobalProviders.data.temperature.isEmpty) { final temperatureList = (await ExpTech().getWeatherList()).reversed.toList(); @@ -134,16 +137,8 @@ class TemperatureMapLayerManager extends MapLayerManager { } if (!isLayerExists) { + // circles final properties = CircleLayerProperties( - circleRadius: [ - Expressions.interpolate, - ['linear'], - [Expressions.zoom], - 7, - 5, - 12, - 15, - ], circleColor: [ Expressions.interpolate, ['linear'], @@ -163,14 +158,57 @@ class TemperatureMapLayerManager extends MapLayerManager { 40, '#8B0000', ], - circleOpacity: 0.7, - circleStrokeWidth: 0.2, - circleStrokeColor: '#000000', - circleStrokeOpacity: 0.7, + circleRadius: kCircleIconSize, + circleOpacity: 0.75, + circleStrokeColor: colors.outlineVariant.toHexStringRGB(), + circleStrokeWidth: 0.5, + circleStrokeOpacity: 0.75, + visibility: visible ? 'visible' : 'none', + ); + + // labels + final temperature = [ + Expressions.caseExpression, + GlobalProviders.ui.useFahrenheit, + [ + Expressions.round, + [ + Expressions.plus, + [ + Expressions.multiply, + [Expressions.get, 'temperature'], + 1.8, + ], + 32, + ], + ], + [Expressions.get, 'temperature'], + ]; + final properties2 = SymbolLayerProperties( + textField: [ + Expressions.concat, + [Expressions.get, 'name'], + '\n', + [Expressions.concat, temperature, if (GlobalProviders.ui.useFahrenheit) '℉' else '℃'], + ], + textSize: 10, + textColor: colors.onSurfaceVariant.toHexStringRGB(), + textHaloColor: colors.outlineVariant.toHexStringRGB(), + textHaloWidth: 1, + textFont: ['Noto Sans TC Bold'], + textOffset: [0, 1], + textAnchor: 'top', visibility: visible ? 'visible' : 'none', ); await controller.addLayer(sourceId, layerId, properties, belowLayerId: BaseMapLayerIds.userLocation); + await controller.addLayer( + sourceId, + '$layerId-label', + properties2, + belowLayerId: BaseMapLayerIds.userLocation, + minzoom: 10, + ); } if (isSourceExists && isLayerExists) return; @@ -189,6 +227,7 @@ class TemperatureMapLayerManager extends MapLayerManager { try { await controller.setLayerVisibility(layerId, false); + await controller.setLayerVisibility('$layerId-label', false); visible = false; } catch (e, s) { @@ -204,6 +243,8 @@ class TemperatureMapLayerManager extends MapLayerManager { try { await controller.setLayerVisibility(layerId, true); + await controller.setLayerVisibility('$layerId-label', true); + await _focus(); visible = true; @@ -219,6 +260,8 @@ class TemperatureMapLayerManager extends MapLayerManager { final sourceId = MapSourceIds.temperature(currentTemperatureTime.value); await controller.removeLayer(layerId); + await controller.removeLayer('$layerId-label'); + await controller.removeSource(sourceId); } catch (e, s) { TalkerManager.instance.error('TemperatureMapLayerManager.remove', e, s); diff --git a/lib/app/map/_lib/managers/tsunami.dart b/lib/app/map/_lib/managers/tsunami.dart index 3156308f6..ed006ce4d 100644 --- a/lib/app/map/_lib/managers/tsunami.dart +++ b/lib/app/map/_lib/managers/tsunami.dart @@ -272,7 +272,7 @@ class _TsunamiMapLayerSheetState extends State { textColor: '#ffffff', textHaloColor: '#000000', textHaloWidth: 1, - textFont: ['Noto Sans Regular'], + textFont: ['Noto Sans TC Bold'], textOffset: [ Expressions.literal, [0, 3.5], diff --git a/lib/app/map/_lib/managers/wind.dart b/lib/app/map/_lib/managers/wind.dart index 3768693be..11160b327 100644 --- a/lib/app/map/_lib/managers/wind.dart +++ b/lib/app/map/_lib/managers/wind.dart @@ -13,7 +13,6 @@ import 'package:dpip/app/map/_widgets/map_legend.dart'; import 'package:dpip/core/i18n.dart'; import 'package:dpip/core/providers.dart'; import 'package:dpip/models/data.dart'; -import 'package:dpip/utils/constants.dart'; import 'package:dpip/utils/extensions/build_context.dart'; import 'package:dpip/utils/extensions/string.dart'; import 'package:dpip/utils/geojson.dart'; @@ -69,6 +68,8 @@ class WindMapLayerManager extends MapLayerManager { Future setup() async { if (didSetup) return; + final colors = context.colors; + try { if (GlobalProviders.data.wind.isEmpty) { final windList = (await ExpTech().getWeatherList()).reversed.toList(); @@ -114,16 +115,55 @@ class WindMapLayerManager extends MapLayerManager { } if (!isLayerExists) { + // arrows final properties = SymbolLayerProperties( iconImage: [Expressions.get, 'icon'], - iconSize: kSymbolIconSize, + iconSize: [ + Expressions.interpolate, + ['linear'], + [Expressions.zoom], + 5, + 0.1, + 15, + 0.8, + ], iconRotate: [Expressions.get, 'wind_direction'], + iconOpacity: 0.75, iconAllowOverlap: true, iconIgnorePlacement: true, visibility: visible ? 'visible' : 'none', ); + // labels + final properties2 = SymbolLayerProperties( + textField: [ + Expressions.concat, + [Expressions.get, 'name'], + '\n', + [ + Expressions.concat, + [Expressions.get, 'wind_speed'], + 'm/s', + ], + ], + textSize: 10, + textColor: colors.onSurfaceVariant.toHexStringRGB(), + textHaloColor: colors.outlineVariant.toHexStringRGB(), + textHaloWidth: 1, + textFont: ['Noto Sans TC Bold'], + textOffset: [0, 2], + textAnchor: 'top', + visibility: visible ? 'visible' : 'none', + ); + await controller.addLayer(sourceId, layerId, properties, belowLayerId: BaseMapLayerIds.userLocation); + await controller.addLayer( + sourceId, + '$layerId-label', + properties2, + belowLayerId: BaseMapLayerIds.userLocation, + minzoom: 10, + ); } if (isSourceExists && isLayerExists) return; @@ -142,6 +182,7 @@ class WindMapLayerManager extends MapLayerManager { try { await controller.setLayerVisibility(layerId, false); + await controller.setLayerVisibility('$layerId-label', false); visible = false; } catch (e, s) { @@ -157,6 +198,7 @@ class WindMapLayerManager extends MapLayerManager { try { await controller.setLayerVisibility(layerId, true); + await controller.setLayerVisibility('$layerId-label', true); visible = true; } catch (e, s) { @@ -171,6 +213,8 @@ class WindMapLayerManager extends MapLayerManager { final sourceId = MapSourceIds.wind(currentWindTime.value); await controller.removeLayer(layerId); + await controller.removeLayer('$layerId-label'); + await controller.removeSource(sourceId); } catch (e, s) { TalkerManager.instance.error('WindMapLayerManager.dispose', e, s); diff --git a/lib/app/map/page.dart b/lib/app/map/page.dart index f051cd207..a20936a7c 100644 --- a/lib/app/map/page.dart +++ b/lib/app/map/page.dart @@ -299,7 +299,7 @@ class _MapPageState extends State with TickerProviderStateMixin { return Scaffold( body: Stack( children: [ - DpipMap(onMapCreated: onMapCreated, tiltGesturesEnabled: true), + DpipMap(onMapCreated: onMapCreated), PositionedLayerButton( activeLayers: _activeLayers, currentBaseMap: _baseMapType, diff --git a/lib/app_old/page/map/tsunami/tsunami.dart b/lib/app_old/page/map/tsunami/tsunami.dart index fc84907fb..e90d87692 100644 --- a/lib/app_old/page/map/tsunami/tsunami.dart +++ b/lib/app_old/page/map/tsunami/tsunami.dart @@ -185,7 +185,7 @@ class _TsunamiMapState extends State { textColor: '#ffffff', textHaloColor: '#000000', textHaloWidth: 1, - textFont: ['Noto Sans Regular'], + textFont: ['Noto Sans TC Bold'], textOffset: [ Expressions.literal, [0, 3.5], diff --git a/lib/app_old/page/map/weather/humidity.dart b/lib/app_old/page/map/weather/humidity.dart index 049f1347f..a3094c25f 100644 --- a/lib/app_old/page/map/weather/humidity.dart +++ b/lib/app_old/page/map/weather/humidity.dart @@ -219,7 +219,7 @@ class _HumidityMapState extends State { textColor: '#ffffff', textHaloColor: '#000000', textHaloWidth: 1, - textFont: ['Noto Sans Regular'], + textFont: ['Noto Sans TC Bold'], textOffset: [ Expressions.literal, [0, 2], diff --git a/lib/app_old/page/map/weather/pressure.dart b/lib/app_old/page/map/weather/pressure.dart index f148181a2..fb37f9d49 100644 --- a/lib/app_old/page/map/weather/pressure.dart +++ b/lib/app_old/page/map/weather/pressure.dart @@ -221,7 +221,7 @@ class _PressureMapState extends State { textColor: '#ffffff', textHaloColor: '#000000', textHaloWidth: 1, - textFont: ['Noto Sans Regular'], + textFont: ['Noto Sans TC Bold'], textOffset: [ Expressions.literal, [0, 2], diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 689f497bc..5fd3d6569 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -16,6 +16,16 @@ const kSymbolIconSize = [ [Expressions.zoom], 5, 0.1, - 10, - 0.6, + 15, + 1, +]; + +const kCircleIconSize = [ + Expressions.interpolate, + ['linear'], + [Expressions.zoom], + 5, + 2, + 15, + 12, ]; diff --git a/lib/widgets/map/map.dart b/lib/widgets/map/map.dart index d7c012ca8..76b4ff074 100644 --- a/lib/widgets/map/map.dart +++ b/lib/widgets/map/map.dart @@ -128,7 +128,7 @@ class DpipMapState extends State { }, }, 'sprite': spritePath, - 'glyphs': 'https://glyphs.geolonia.com/{fontstack}/{range}.pbf', + 'glyphs': 'https://cdn.jsdelivr.net/gh/exptechtw/map-glyph/{fontstack}/{range}.pbf', 'layers': [ { 'id': 'background', @@ -328,7 +328,7 @@ class DpipMapState extends State { final double adjustedZoomValue = adjustedZoom(widget.initialCameraPosition.zoom); return MapLibreMap( - minMaxZoomPreference: widget.minMaxZoomPreference ?? const MinMaxZoomPreference(3, 9), + minMaxZoomPreference: widget.minMaxZoomPreference ?? const MinMaxZoomPreference(4, 12), trackCameraPosition: true, initialCameraPosition: CameraPosition(target: widget.initialCameraPosition.target, zoom: adjustedZoomValue), styleString: styleAbsoluteFilePath!, diff --git a/pubspec.lock b/pubspec.lock index c95179260..d97c17bc5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -507,38 +507,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.3" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - sha256: "33b3e0269ae9d51669957a923f2376bee96299b09915d856395af8c4238aebfa" - url: "https://pub.dev" - source: hosted - version: "19.1.0" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "2569b973fc9d1f63a37410a9f7c1c552081226c597190cb359ef5d5762d1631c" - url: "https://pub.dev" - source: hosted - version: "9.0.0" - flutter_local_notifications_windows: - dependency: transitive - description: - name: flutter_local_notifications_windows - sha256: f8fc0652a601f83419d623c85723a3e82ad81f92b33eaa9bcc21ea1b94773e6e - url: "https://pub.dev" - source: hosted - version: "1.0.0" flutter_localizations: dependency: "direct main" description: flutter @@ -838,26 +806,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lint: dependency: "direct dev" description: @@ -1435,10 +1403,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.4" timezone: dependency: "direct main" description: @@ -1571,10 +1539,10 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" vm_service: dependency: transitive description: @@ -1664,5 +1632,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0-0 <4.0.0" + dart: ">=3.7.0 <4.0.0" flutter: ">=3.31.0-0.0.pre"