From d3bf19492efd4f252d385e65a63ac1efb47289c2 Mon Sep 17 00:00:00 2001 From: YumNumm Date: Mon, 9 Sep 2024 02:40:38 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=B7=9D=E9=9B=A2=E6=B8=9B=E8=A1=B0?= =?UTF-8?q?=E5=BC=8F=E3=81=AE=E4=BA=88=E6=83=B3=E9=9C=87=E5=BA=A6=E6=8F=8F?= =?UTF-8?q?=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/estimated_intensity_data_source.dart | 30 +- .../estimated_intensity_provider.dart | 200 ++++++++-- .../estimated_intensity_provider.freezed.dart | 230 +++++++++++ .../estimated_intensity_provider.g.dart | 42 ++- .../eew_settings/eew_settings_notifier.dart | 36 ++ .../eew_settings/eew_settings_notifier.g.dart | 29 ++ .../model/eew_setitngs_model.dart | 15 + .../model/eew_setitngs_model.freezed.dart | 195 ++++++++++ .../model/eew_setitngs_model.g.dart | 30 ++ .../kmoni/viewmodel/kmoni_settings.dart | 1 + .../home/features/map/view/main_map_view.dart | 2 - .../map/viewmodel/main_map_viewmodel.dart | 356 ++++++++++++++---- .../map/viewmodel/main_map_viewmodel.g.dart | 2 +- app/lib/feature/home/view/home_view.dart | 1 + .../children/config/debug/debugger_page.dart | 17 + .../provider/shake_detection_provider.dart | 4 - .../provider/shake_detection_provider.g.dart | 2 +- app/pubspec.lock | 8 + app/pubspec.yaml | 1 + packages/eqapi_types/lib/src/model/core.dart | 15 + .../lib/converter/earthquake.dart | 3 +- .../updater.sh | 6 +- 22 files changed, 1077 insertions(+), 148 deletions(-) create mode 100644 app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.freezed.dart create mode 100644 app/lib/feature/home/features/eew_settings/eew_settings_notifier.dart create mode 100644 app/lib/feature/home/features/eew_settings/eew_settings_notifier.g.dart create mode 100644 app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.dart create mode 100644 app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.freezed.dart create mode 100644 app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.g.dart diff --git a/app/lib/core/provider/estimated_intensity/data/estimated_intensity_data_source.dart b/app/lib/core/provider/estimated_intensity/data/estimated_intensity_data_source.dart index 1614dde53..8144804eb 100644 --- a/app/lib/core/provider/estimated_intensity/data/estimated_intensity_data_source.dart +++ b/app/lib/core/provider/estimated_intensity/data/estimated_intensity_data_source.dart @@ -1,8 +1,5 @@ import 'dart:math' as math; -import 'package:eqmonitor/core/provider/kmoni_observation_points/model/kmoni_observation_point.dart'; -import 'package:kyoshin_observation_point_types/kyoshin_observation_point.pb.dart'; -import 'package:lat_lng/lat_lng.dart'; import 'package:latlong2/latlong.dart' as lat_long_2; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -14,26 +11,31 @@ EstimatedIntensityDataSource estimatedIntensityDataSource( ) => EstimatedIntensityDataSource(); +typedef CalculationPoint = ({ + double lat, + double lon, + double arv400, +}); + class EstimatedIntensityDataSource { - List getEstimatedIntensity({ - required List points, + Iterable getEstimatedIntensity({ + required List points, required double jmaMagnitude, required int depth, - required LatLng hypocenter, - }) { + required ({double lat, double lon}) hypocenter, + }) sync* { // Mjma(気象庁マグニチュード)->Mw(モーメントマグニチュード) // 宇津(1982)の経験式を用いる final momentMagnitude = jmaMagnitude - 0.171; // 断層長計算(半径) final faultLength = math.pow(10, 0.5 * momentMagnitude - 1.85) / 2; - final result = []; const distanceCalcular = lat_long_2.Distance(); for (final point in points) { final epicenterDistance = distanceCalcular.as( lat_long_2.LengthUnit.Kilometer, lat_long_2.LatLng( - point.location.latitude, - point.location.longitude, + point.lat, + point.lon, ), lat_long_2.LatLng(hypocenter.lat, hypocenter.lon), ) - @@ -63,14 +65,8 @@ class EstimatedIntensityDataSource { //* 予測する地点の地表面推定最大速度から計測震度への変換 final intensity = 2.68 + 1.72 * log10(pgv); - result.add( - AnalyzedKmoniObservationPoint( - point: point, - intensityValue: intensity, - ), - ); + yield intensity; } - return result; } } diff --git a/app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.dart b/app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.dart index 74f0c0bd2..0489fd845 100644 --- a/app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.dart +++ b/app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.dart @@ -1,66 +1,190 @@ // ignore_for_file: provider_dependencies import 'dart:developer'; +import 'dart:math' as math; import 'package:eqapi_types/eqapi_types.dart'; import 'package:eqmonitor/core/provider/eew/eew_alive_telegram.dart'; import 'package:eqmonitor/core/provider/estimated_intensity/data/estimated_intensity_data_source.dart'; -import 'package:eqmonitor/core/provider/kmoni_observation_points/model/kmoni_observation_point.dart'; -import 'package:eqmonitor/core/provider/kmoni_observation_points/provider/kyoshin_observation_points_provider.dart'; -import 'package:lat_lng/lat_lng.dart'; +import 'package:eqmonitor/core/provider/jma_parameter/jma_parameter.dart'; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:jma_parameter_api_client/jma_parameter_api_client.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +part 'estimated_intensity_provider.freezed.dart'; part 'estimated_intensity_provider.g.dart'; +typedef _CachedPoint = ({ + String regionCode, + String cityCode, + EarthquakeParameterStationItem station, +}); + @Riverpod(keepAlive: true) class EstimatedIntensity extends _$EstimatedIntensity { @override - List build() { - ref.listen(eewAliveTelegramProvider, (previous, next) { - log('EEW Telegram updated: $next'); - state = calc(next ?? []); + Future> build() async { + ref.listen(eewAliveTelegramProvider, (_, next) async { + final parameter = await ref.read(jmaParameterProvider.future); + final result = await calcInIsolate( + next ?? [], + parameter.earthquake, + ); + state = AsyncData(result.toList()); }); - return calc(ref.read(eewAliveTelegramProvider) ?? []); + final parameter = await ref.read(jmaParameterProvider.future); + + final result = await calcInIsolate( + ref.read(eewAliveTelegramProvider) ?? [], + parameter.earthquake, + ); + return result.toList(); } - List calc(List eews) { - final points = ref.read(kyoshinObservationPointsProvider); - final results = >[]; - for (final eew in eews.where( + List<_CachedPoint>? _cachedPoints; + List? _calculationPoints; + + List calc( + List eews, + EarthquakeParameter parameter, + ) { + // 計算前にPointを用意 + _cachedPoints ??= _generateCachedPoints(parameter); + _calculationPoints ??= _generateCalculationPoints(_cachedPoints!); + + final calculator = ref.read(estimatedIntensityDataSourceProvider); + final results = >[]; + + final targetEews = eews.where( (e) => !e.isCanceled && e.latitude != null && e.longitude != null, - )) { - results.add( - ref.read(estimatedIntensityDataSourceProvider).getEstimatedIntensity( - points: points.points, - jmaMagnitude: eew.magnitude ?? 0, - depth: eew.depth ?? 0, - hypocenter: LatLng(eew.latitude!, eew.longitude!), - ), - ); + ); + if (targetEews.isEmpty) { + return []; } - final result = _merge(results) - ..sort((a, b) => b.intensityValue!.compareTo(a.intensityValue!)); + for (final eew in targetEews) { + final result = calculator.getEstimatedIntensity( + points: _calculationPoints!.toList(), + jmaMagnitude: eew.magnitude!, + depth: eew.depth!, + hypocenter: (lat: eew.latitude!, lon: eew.longitude!), + ).toList(); + results.add(result); + } + + // resultsのIterableそれぞれは同じ長さであることを確認 + assert(results.every((e) => e.length == _calculationPoints!.length)); + + final result = []; + // それぞれについて最大の値を取る + for (var index = 0; index < results.first.length; index++) { + final values = results.map((e) => e[index]); + final max = values.reduce(math.max); + result.add( + EstimatedIntensityPoint( + regionCode: _cachedPoints![index].regionCode, + cityCode: _cachedPoints![index].cityCode, + station: _cachedPoints![index].station, + intensity: max, + ), + ); + } return result; } - /// 同一観測点の結果をマージする - /// より大きい震度を返す - List _merge( - List> results, + Future> calcInIsolate( + List eews, + EarthquakeParameter parameter, + ) async => + // TODO(YumNumm): 並列計算 + calc(eews, parameter); + + List<_CachedPoint> _generateCachedPoints( + EarthquakeParameter earthquake, ) { - final merged = []; - for (final result in results) { - for (final point in result) { - final index = merged.indexWhere((e) => e.point == point.point); - if (index == -1) { - merged.add(point); - } else { - merged[index] = merged[index].intensityValue! > point.intensityValue! - ? merged[index] - : point; + final result = <_CachedPoint>[]; + for (final region in earthquake.regions) { + for (final city in region.cities) { + for (final station in city.stations) { + result.add( + ( + regionCode: region.code, + cityCode: city.code, + station: station, + ), + ); } } } - return merged; + return result; } + + List _generateCalculationPoints( + Iterable<_CachedPoint> points, + ) { + final result = []; + for (final point in points) { + result.add( + ( + lat: point.station.latitude, + lon: point.station.longitude, + arv400: point.station.arv400, + ), + ); + } + return result; + } +} + +@Riverpod(keepAlive: true) +Stream> estimatedIntensityCity( + EstimatedIntensityCityRef ref, +) async* { + final estimatedIntensity = ref.watch(estimatedIntensityProvider).valueOrNull; + if (estimatedIntensity != null) { + final map = {}; + for (final item in estimatedIntensity) { + final currentValue = map[item.cityCode]; + if (currentValue == null) { + map[item.cityCode] = item.intensity; + } else { + map[item.cityCode] = math.max(currentValue, item.intensity); + } + } + yield map; + } +} + +@Riverpod(keepAlive: true) +Stream> estimatedIntensityRegion( + EstimatedIntensityRegionRef ref, +) async* { + final estimatedIntensity = ref.watch(estimatedIntensityProvider).valueOrNull; + log( + 'estimatedIntensityRegion: ${estimatedIntensity.runtimeType}, ${estimatedIntensity?.length}', + name: 'estimatedIntensityRegion', + ); + if (estimatedIntensity != null) { + final map = {}; + for (final item in estimatedIntensity) { + final currentValue = map[item.regionCode]; + if (currentValue == null) { + map[item.regionCode] = item.intensity; + } else { + map[item.regionCode] = math.max(currentValue, item.intensity); + } + } + log('estimatedIntensityRegion: ${map.entries.length}'); + yield map; + } +} + +@Freezed(toJson: false) +class EstimatedIntensityPoint with _$EstimatedIntensityPoint { + const factory EstimatedIntensityPoint({ + required String regionCode, + required String cityCode, + required EarthquakeParameterStationItem station, + required double intensity, + }) = _EstimatedIntensityPoint; } diff --git a/app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.freezed.dart b/app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.freezed.dart new file mode 100644 index 000000000..dfffa81b4 --- /dev/null +++ b/app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.freezed.dart @@ -0,0 +1,230 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'estimated_intensity_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$EstimatedIntensityPoint { + String get regionCode => throw _privateConstructorUsedError; + String get cityCode => throw _privateConstructorUsedError; + EarthquakeParameterStationItem get station => + throw _privateConstructorUsedError; + double get intensity => throw _privateConstructorUsedError; + + /// Create a copy of EstimatedIntensityPoint + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $EstimatedIntensityPointCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $EstimatedIntensityPointCopyWith<$Res> { + factory $EstimatedIntensityPointCopyWith(EstimatedIntensityPoint value, + $Res Function(EstimatedIntensityPoint) then) = + _$EstimatedIntensityPointCopyWithImpl<$Res, EstimatedIntensityPoint>; + @useResult + $Res call( + {String regionCode, + String cityCode, + EarthquakeParameterStationItem station, + double intensity}); +} + +/// @nodoc +class _$EstimatedIntensityPointCopyWithImpl<$Res, + $Val extends EstimatedIntensityPoint> + implements $EstimatedIntensityPointCopyWith<$Res> { + _$EstimatedIntensityPointCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of EstimatedIntensityPoint + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? regionCode = null, + Object? cityCode = null, + Object? station = null, + Object? intensity = null, + }) { + return _then(_value.copyWith( + regionCode: null == regionCode + ? _value.regionCode + : regionCode // ignore: cast_nullable_to_non_nullable + as String, + cityCode: null == cityCode + ? _value.cityCode + : cityCode // ignore: cast_nullable_to_non_nullable + as String, + station: null == station + ? _value.station + : station // ignore: cast_nullable_to_non_nullable + as EarthquakeParameterStationItem, + intensity: null == intensity + ? _value.intensity + : intensity // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$EstimatedIntensityPointImplCopyWith<$Res> + implements $EstimatedIntensityPointCopyWith<$Res> { + factory _$$EstimatedIntensityPointImplCopyWith( + _$EstimatedIntensityPointImpl value, + $Res Function(_$EstimatedIntensityPointImpl) then) = + __$$EstimatedIntensityPointImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String regionCode, + String cityCode, + EarthquakeParameterStationItem station, + double intensity}); +} + +/// @nodoc +class __$$EstimatedIntensityPointImplCopyWithImpl<$Res> + extends _$EstimatedIntensityPointCopyWithImpl<$Res, + _$EstimatedIntensityPointImpl> + implements _$$EstimatedIntensityPointImplCopyWith<$Res> { + __$$EstimatedIntensityPointImplCopyWithImpl( + _$EstimatedIntensityPointImpl _value, + $Res Function(_$EstimatedIntensityPointImpl) _then) + : super(_value, _then); + + /// Create a copy of EstimatedIntensityPoint + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? regionCode = null, + Object? cityCode = null, + Object? station = null, + Object? intensity = null, + }) { + return _then(_$EstimatedIntensityPointImpl( + regionCode: null == regionCode + ? _value.regionCode + : regionCode // ignore: cast_nullable_to_non_nullable + as String, + cityCode: null == cityCode + ? _value.cityCode + : cityCode // ignore: cast_nullable_to_non_nullable + as String, + station: null == station + ? _value.station + : station // ignore: cast_nullable_to_non_nullable + as EarthquakeParameterStationItem, + intensity: null == intensity + ? _value.intensity + : intensity // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc + +class _$EstimatedIntensityPointImpl + with DiagnosticableTreeMixin + implements _EstimatedIntensityPoint { + const _$EstimatedIntensityPointImpl( + {required this.regionCode, + required this.cityCode, + required this.station, + required this.intensity}); + + @override + final String regionCode; + @override + final String cityCode; + @override + final EarthquakeParameterStationItem station; + @override + final double intensity; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'EstimatedIntensityPoint(regionCode: $regionCode, cityCode: $cityCode, station: $station, intensity: $intensity)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'EstimatedIntensityPoint')) + ..add(DiagnosticsProperty('regionCode', regionCode)) + ..add(DiagnosticsProperty('cityCode', cityCode)) + ..add(DiagnosticsProperty('station', station)) + ..add(DiagnosticsProperty('intensity', intensity)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$EstimatedIntensityPointImpl && + (identical(other.regionCode, regionCode) || + other.regionCode == regionCode) && + (identical(other.cityCode, cityCode) || + other.cityCode == cityCode) && + (identical(other.station, station) || other.station == station) && + (identical(other.intensity, intensity) || + other.intensity == intensity)); + } + + @override + int get hashCode => + Object.hash(runtimeType, regionCode, cityCode, station, intensity); + + /// Create a copy of EstimatedIntensityPoint + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$EstimatedIntensityPointImplCopyWith<_$EstimatedIntensityPointImpl> + get copyWith => __$$EstimatedIntensityPointImplCopyWithImpl< + _$EstimatedIntensityPointImpl>(this, _$identity); +} + +abstract class _EstimatedIntensityPoint implements EstimatedIntensityPoint { + const factory _EstimatedIntensityPoint( + {required final String regionCode, + required final String cityCode, + required final EarthquakeParameterStationItem station, + required final double intensity}) = _$EstimatedIntensityPointImpl; + + @override + String get regionCode; + @override + String get cityCode; + @override + EarthquakeParameterStationItem get station; + @override + double get intensity; + + /// Create a copy of EstimatedIntensityPoint + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$EstimatedIntensityPointImplCopyWith<_$EstimatedIntensityPointImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.g.dart b/app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.g.dart index b6c766cc5..5d7bf968d 100644 --- a/app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.g.dart +++ b/app/lib/core/provider/estimated_intensity/provider/estimated_intensity_provider.g.dart @@ -8,13 +8,47 @@ part of 'estimated_intensity_provider.dart'; // RiverpodGenerator // ************************************************************************** +String _$estimatedIntensityCityHash() => + r'6d6bcad277d483556c0ef8296acd134e64ed5f4e'; + +/// See also [estimatedIntensityCity]. +@ProviderFor(estimatedIntensityCity) +final estimatedIntensityCityProvider = + StreamProvider>.internal( + estimatedIntensityCity, + name: r'estimatedIntensityCityProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$estimatedIntensityCityHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef EstimatedIntensityCityRef = StreamProviderRef>; +String _$estimatedIntensityRegionHash() => + r'98e57c27650c780f6cd1fb1e375462526d727470'; + +/// See also [estimatedIntensityRegion]. +@ProviderFor(estimatedIntensityRegion) +final estimatedIntensityRegionProvider = + StreamProvider>.internal( + estimatedIntensityRegion, + name: r'estimatedIntensityRegionProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$estimatedIntensityRegionHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef EstimatedIntensityRegionRef = StreamProviderRef>; String _$estimatedIntensityHash() => - r'7338ee8f91639dff794d0a8a938ae7dd3b9c80a8'; + r'4556cd1859ef20b7583c73527419d394d3cacaf7'; /// See also [EstimatedIntensity]. @ProviderFor(EstimatedIntensity) -final estimatedIntensityProvider = NotifierProvider>.internal( +final estimatedIntensityProvider = AsyncNotifierProvider>.internal( EstimatedIntensity.new, name: r'estimatedIntensityProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') @@ -24,6 +58,6 @@ final estimatedIntensityProvider = NotifierProvider>; +typedef _$EstimatedIntensity = AsyncNotifier>; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/app/lib/feature/home/features/eew_settings/eew_settings_notifier.dart b/app/lib/feature/home/features/eew_settings/eew_settings_notifier.dart new file mode 100644 index 000000000..1e95d37e9 --- /dev/null +++ b/app/lib/feature/home/features/eew_settings/eew_settings_notifier.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; + +import 'package:eqmonitor/core/provider/shared_preferences.dart'; +import 'package:eqmonitor/feature/home/features/eew_settings/model/eew_setitngs_model.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'eew_settings_notifier.g.dart'; + +@Riverpod(keepAlive: true) +class EewSettingsNotifier extends _$EewSettingsNotifier { + @override + EewSetitngs build() { + final prefs = ref.read(sharedPreferencesProvider); + final json = prefs.getString(_prefsKey); + if (json == null) { + return const EewSetitngs(); + } + return EewSetitngs.fromJson(jsonDecode(json) as Map); + } + + Future setShowCalculatedRegionIntensity({required bool value}) async { + state = state.copyWith(showCalculatedRegionIntensity: value); + await _save(); + } + + Future setShowCalculatedCityIntensity({required bool value}) async { + state = state.copyWith(showCalculatedCityIntensity: value); + await _save(); + } + + Future _save() async => ref + .read(sharedPreferencesProvider) + .setString(_prefsKey, jsonEncode(state.toJson())); + + static const _prefsKey = 'eew_settings'; +} diff --git a/app/lib/feature/home/features/eew_settings/eew_settings_notifier.g.dart b/app/lib/feature/home/features/eew_settings/eew_settings_notifier.g.dart new file mode 100644 index 000000000..afd998d07 --- /dev/null +++ b/app/lib/feature/home/features/eew_settings/eew_settings_notifier.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: type=lint, duplicate_ignore + +part of 'eew_settings_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$eewSettingsNotifierHash() => + r'b3dccfd4033a5ab9668b727ee336139cf358edb0'; + +/// See also [EewSettingsNotifier]. +@ProviderFor(EewSettingsNotifier) +final eewSettingsNotifierProvider = + NotifierProvider.internal( + EewSettingsNotifier.new, + name: r'eewSettingsNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$eewSettingsNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$EewSettingsNotifier = Notifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.dart b/app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.dart new file mode 100644 index 000000000..68bb9493a --- /dev/null +++ b/app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'eew_setitngs_model.freezed.dart'; +part 'eew_setitngs_model.g.dart'; + +@freezed +class EewSetitngs with _$EewSetitngs { + const factory EewSetitngs({ + @Default(false) bool showCalculatedRegionIntensity, + @Default(false) bool showCalculatedCityIntensity, + }) = _EewSetitngs; + + factory EewSetitngs.fromJson(Map json) => + _$EewSetitngsFromJson(json); +} diff --git a/app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.freezed.dart b/app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.freezed.dart new file mode 100644 index 000000000..81c97f74e --- /dev/null +++ b/app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.freezed.dart @@ -0,0 +1,195 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'eew_setitngs_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +EewSetitngs _$EewSetitngsFromJson(Map json) { + return _EewSetitngs.fromJson(json); +} + +/// @nodoc +mixin _$EewSetitngs { + bool get showCalculatedRegionIntensity => throw _privateConstructorUsedError; + bool get showCalculatedCityIntensity => throw _privateConstructorUsedError; + + /// Serializes this EewSetitngs to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of EewSetitngs + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $EewSetitngsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $EewSetitngsCopyWith<$Res> { + factory $EewSetitngsCopyWith( + EewSetitngs value, $Res Function(EewSetitngs) then) = + _$EewSetitngsCopyWithImpl<$Res, EewSetitngs>; + @useResult + $Res call( + {bool showCalculatedRegionIntensity, bool showCalculatedCityIntensity}); +} + +/// @nodoc +class _$EewSetitngsCopyWithImpl<$Res, $Val extends EewSetitngs> + implements $EewSetitngsCopyWith<$Res> { + _$EewSetitngsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of EewSetitngs + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? showCalculatedRegionIntensity = null, + Object? showCalculatedCityIntensity = null, + }) { + return _then(_value.copyWith( + showCalculatedRegionIntensity: null == showCalculatedRegionIntensity + ? _value.showCalculatedRegionIntensity + : showCalculatedRegionIntensity // ignore: cast_nullable_to_non_nullable + as bool, + showCalculatedCityIntensity: null == showCalculatedCityIntensity + ? _value.showCalculatedCityIntensity + : showCalculatedCityIntensity // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$EewSetitngsImplCopyWith<$Res> + implements $EewSetitngsCopyWith<$Res> { + factory _$$EewSetitngsImplCopyWith( + _$EewSetitngsImpl value, $Res Function(_$EewSetitngsImpl) then) = + __$$EewSetitngsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {bool showCalculatedRegionIntensity, bool showCalculatedCityIntensity}); +} + +/// @nodoc +class __$$EewSetitngsImplCopyWithImpl<$Res> + extends _$EewSetitngsCopyWithImpl<$Res, _$EewSetitngsImpl> + implements _$$EewSetitngsImplCopyWith<$Res> { + __$$EewSetitngsImplCopyWithImpl( + _$EewSetitngsImpl _value, $Res Function(_$EewSetitngsImpl) _then) + : super(_value, _then); + + /// Create a copy of EewSetitngs + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? showCalculatedRegionIntensity = null, + Object? showCalculatedCityIntensity = null, + }) { + return _then(_$EewSetitngsImpl( + showCalculatedRegionIntensity: null == showCalculatedRegionIntensity + ? _value.showCalculatedRegionIntensity + : showCalculatedRegionIntensity // ignore: cast_nullable_to_non_nullable + as bool, + showCalculatedCityIntensity: null == showCalculatedCityIntensity + ? _value.showCalculatedCityIntensity + : showCalculatedCityIntensity // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$EewSetitngsImpl implements _EewSetitngs { + const _$EewSetitngsImpl( + {this.showCalculatedRegionIntensity = false, + this.showCalculatedCityIntensity = false}); + + factory _$EewSetitngsImpl.fromJson(Map json) => + _$$EewSetitngsImplFromJson(json); + + @override + @JsonKey() + final bool showCalculatedRegionIntensity; + @override + @JsonKey() + final bool showCalculatedCityIntensity; + + @override + String toString() { + return 'EewSetitngs(showCalculatedRegionIntensity: $showCalculatedRegionIntensity, showCalculatedCityIntensity: $showCalculatedCityIntensity)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$EewSetitngsImpl && + (identical(other.showCalculatedRegionIntensity, + showCalculatedRegionIntensity) || + other.showCalculatedRegionIntensity == + showCalculatedRegionIntensity) && + (identical(other.showCalculatedCityIntensity, + showCalculatedCityIntensity) || + other.showCalculatedCityIntensity == + showCalculatedCityIntensity)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, showCalculatedRegionIntensity, showCalculatedCityIntensity); + + /// Create a copy of EewSetitngs + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$EewSetitngsImplCopyWith<_$EewSetitngsImpl> get copyWith => + __$$EewSetitngsImplCopyWithImpl<_$EewSetitngsImpl>(this, _$identity); + + @override + Map toJson() { + return _$$EewSetitngsImplToJson( + this, + ); + } +} + +abstract class _EewSetitngs implements EewSetitngs { + const factory _EewSetitngs( + {final bool showCalculatedRegionIntensity, + final bool showCalculatedCityIntensity}) = _$EewSetitngsImpl; + + factory _EewSetitngs.fromJson(Map json) = + _$EewSetitngsImpl.fromJson; + + @override + bool get showCalculatedRegionIntensity; + @override + bool get showCalculatedCityIntensity; + + /// Create a copy of EewSetitngs + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$EewSetitngsImplCopyWith<_$EewSetitngsImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.g.dart b/app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.g.dart new file mode 100644 index 000000000..b80b2c354 --- /dev/null +++ b/app/lib/feature/home/features/eew_settings/model/eew_setitngs_model.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: type=lint, duplicate_ignore + +part of 'eew_setitngs_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$EewSetitngsImpl _$$EewSetitngsImplFromJson(Map json) => + $checkedCreate( + r'_$EewSetitngsImpl', + json, + ($checkedConvert) { + final val = _$EewSetitngsImpl( + showCalculatedRegionIntensity: $checkedConvert( + 'showCalculatedRegionIntensity', (v) => v as bool? ?? false), + showCalculatedCityIntensity: $checkedConvert( + 'showCalculatedCityIntensity', (v) => v as bool? ?? false), + ); + return val; + }, + ); + +Map _$$EewSetitngsImplToJson(_$EewSetitngsImpl instance) => + { + 'showCalculatedRegionIntensity': instance.showCalculatedRegionIntensity, + 'showCalculatedCityIntensity': instance.showCalculatedCityIntensity, + }; diff --git a/app/lib/feature/home/features/kmoni/viewmodel/kmoni_settings.dart b/app/lib/feature/home/features/kmoni/viewmodel/kmoni_settings.dart index 354c1e183..82555e4ed 100644 --- a/app/lib/feature/home/features/kmoni/viewmodel/kmoni_settings.dart +++ b/app/lib/feature/home/features/kmoni/viewmodel/kmoni_settings.dart @@ -18,6 +18,7 @@ class KmoniSettingsState with _$KmoniSettingsState { /// 強震モニタを使用するかどうか @Default(false) bool useKmoni, + }) = _KmoniSettingsState; factory KmoniSettingsState.fromJson(Map json) => diff --git a/app/lib/feature/home/features/map/view/main_map_view.dart b/app/lib/feature/home/features/map/view/main_map_view.dart index d77636b7f..92b70972f 100644 --- a/app/lib/feature/home/features/map/view/main_map_view.dart +++ b/app/lib/feature/home/features/map/view/main_map_view.dart @@ -4,7 +4,6 @@ import 'dart:developer'; import 'package:eqmonitor/core/provider/capture/intensity_icon_render.dart'; import 'package:eqmonitor/core/provider/debugger/debugger_provider.dart'; -import 'package:eqmonitor/core/provider/estimated_intensity/provider/estimated_intensity_provider.dart'; import 'package:eqmonitor/core/provider/map/map_style.dart'; import 'package:eqmonitor/core/provider/ntp/ntp_provider.dart'; import 'package:eqmonitor/core/provider/travel_time/provider/travel_time_provider.dart'; @@ -244,7 +243,6 @@ class _MapDebugWidget extends HookConsumerWidget { }, ), Text( - 'EewEstimatedIntensity: ${ref.watch(estimatedIntensityProvider).firstOrNull}\n' 'Websocket: ${ref.watch(websocketStatusProvider)}\n', style: const TextStyle( fontSize: 10, diff --git a/app/lib/feature/home/features/map/viewmodel/main_map_viewmodel.dart b/app/lib/feature/home/features/map/viewmodel/main_map_viewmodel.dart index 57888b0df..061bb6c08 100644 --- a/app/lib/feature/home/features/map/viewmodel/main_map_viewmodel.dart +++ b/app/lib/feature/home/features/map/viewmodel/main_map_viewmodel.dart @@ -1,5 +1,4 @@ // ignore_for_file: provider_dependencies -import 'dart:convert'; import 'dart:developer'; import 'dart:typed_data'; @@ -13,6 +12,8 @@ import 'package:eqmonitor/core/provider/estimated_intensity/provider/estimated_i import 'package:eqmonitor/core/provider/kmoni_observation_points/model/kmoni_observation_point.dart'; import 'package:eqmonitor/core/provider/map/map_style.dart'; import 'package:eqmonitor/core/provider/travel_time/provider/travel_time_provider.dart'; +import 'package:eqmonitor/feature/home/features/eew_settings/eew_settings_notifier.dart'; +import 'package:eqmonitor/feature/home/features/eew_settings/model/eew_setitngs_model.dart'; import 'package:eqmonitor/feature/home/features/kmoni/provider/kmoni_view_model.dart'; import 'package:eqmonitor/feature/home/features/kmoni/viewmodel/kmoni_settings.dart'; import 'package:eqmonitor/feature/home/features/map/model/main_map_viewmodel_state.dart'; @@ -26,6 +27,7 @@ import 'package:latlong2/latlong.dart' as latlong2; import 'package:maplibre_gl/maplibre_gl.dart' as maplibre_gl; import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:synchronized/synchronized.dart'; part 'main_map_viewmodel.freezed.dart'; part 'main_map_viewmodel.g.dart'; @@ -51,13 +53,6 @@ class MainMapViewModel extends _$MainMapViewModel { eewAliveTelegramProvider, (_, value) => _onEewStateChanged(value ?? []), ) - ..listen( - estimatedIntensityProvider, - (current, value) => _onEstimatedIntensityChanged( - value, - current?.length != value.length, - ), - ) ..listen( kmoniSettingsProvider.select((e) => e.useKmoni), (_, value) => _onKmoniSettingsChanged(value: value), @@ -65,7 +60,40 @@ class MainMapViewModel extends _$MainMapViewModel { ..listen( shakeDetectionKmoniPointsMergedProvider, (_, value) => _onShakeDetectionStateChanged(value.valueOrNull ?? []), - ); + ) + ..listen( + eewSettingsNotifierProvider, + (_, value) => _onEewSettingsChanged(value), + ) + ..listen( + estimatedIntensityRegionProvider, + (_, state) { + final eewSettings = ref.read(eewSettingsNotifierProvider); + if (!eewSettings.showCalculatedRegionIntensity) { + return; + } + + if (state case AsyncData(:final value)) { + _onEstimatedIntensityRegionChanged(value); + } + }, + ) + ..listen( + estimatedIntensityCityProvider, + (_, state) { + final eewSettings = ref.read(eewSettingsNotifierProvider); + if (!eewSettings.showCalculatedCityIntensity) { + return; + } + + if (state case AsyncData(:final value)) { + _onEstimatedIntensityCityChanged(value); + } + }, + ) + ..listen(eewSettingsNotifierProvider, (_, next) { + _onEewSettingsChanged(next); + }); return MainMapViewmodelState( isHomePosition: true, homeBoundary: defaultBoundary, @@ -103,6 +131,14 @@ class MainMapViewModel extends _$MainMapViewModel { controller: controller, intensityColorModel: ref.watch(intensityColorProvider), ); + _eewEstimatedIntensityCalculatedRegionService = + _EewEstimatedIntensityCalculatedRegionService( + controller: controller, + ); + _eewEstimatedIntensityCalculatedCityService = + _EewEstimatedIntensityCalculatedCityService( + controller: controller, + ); await ( _kmoniObservationPointService!.init(), @@ -113,6 +149,12 @@ class MainMapViewModel extends _$MainMapViewModel { _shakeDetectionBorderService!.init( ref.read(shakeDetectionKmoniPointsMergedProvider).valueOrNull ?? [], ), + _eewEstimatedIntensityCalculatedRegionService!.init( + ref.read(intensityColorProvider), + ), + _eewEstimatedIntensityCalculatedCityService!.init( + ref.read(intensityColorProvider), + ), ).wait; await _eewHypocenterService!.init( hypocenterIcon: ref.read(hypocenterIconRenderProvider)!, @@ -142,15 +184,9 @@ class MainMapViewModel extends _$MainMapViewModel { final aliveEews = ref.read(eewAliveTelegramProvider); if (aliveEews != null && aliveEews.isNotEmpty) { - await ( - _onEewStateChanged( - ref.read(eewAliveTelegramProvider) ?? [], - ), - _onEstimatedIntensityChanged( - ref.read(estimatedIntensityProvider) ?? [], - true, - ) - ).wait; + await _onEewStateChanged( + ref.read(eewAliveTelegramProvider) ?? [], + ); } else { await moveToHomeBoundary(); } @@ -179,6 +215,11 @@ class MainMapViewModel extends _$MainMapViewModel { _EewHypocenterService? _eewHypocenterService; _EewPsWaveService? _eewPsWaveService; late _EewEstimatedIntensityService _eewEstimatedIntensityService; + _EewEstimatedIntensityCalculatedCityService? + _eewEstimatedIntensityCalculatedCityService; + + _EewEstimatedIntensityCalculatedRegionService? + _eewEstimatedIntensityCalculatedRegionService; Future _onEewStateChanged(List values) async { // 初期化が終わっていない場合は何もしない @@ -208,71 +249,44 @@ class MainMapViewModel extends _$MainMapViewModel { await _eewEstimatedIntensityService.update(transformed); } - Future _onEstimatedIntensityChanged( - List points, - bool isForce, - ) async { - final boundary = _getEstimatedIntensityBoundary(points); - try { - await changeHomeBoundaryWithAnimation( - bounds: boundary ?? defaultBoundary, - isForce: isForce, + Future _onEewSettingsChanged(EewSetitngs value) async { + final colorModel = ref.read(intensityColorProvider); + if (value.showCalculatedRegionIntensity) { + _eewEstimatedIntensityCalculatedRegionService ??= + _EewEstimatedIntensityCalculatedRegionService( + controller: _controller!, ); - } on Exception catch (e) { - log('error $e'); + await _eewEstimatedIntensityCalculatedRegionService!.init(colorModel); + } else { + await _eewEstimatedIntensityCalculatedRegionService?.dispose(); + _eewEstimatedIntensityCalculatedRegionService = null; } - } - LatLngBounds? _getEstimatedIntensityBoundary( - List points, - ) { - if (points.isEmpty) { - return null; + if (value.showCalculatedCityIntensity) { + _eewEstimatedIntensityCalculatedCityService ??= + _EewEstimatedIntensityCalculatedCityService( + controller: _controller!, + ); + await _eewEstimatedIntensityCalculatedCityService!.init(colorModel); + } else { + await _eewEstimatedIntensityCalculatedCityService?.dispose(); + _eewEstimatedIntensityCalculatedCityService = null; } - final aliveEews = ref.read(eewAliveTelegramProvider); - - final coords = aliveEews - ?.where((e) => e.latitude != null && e.longitude != null) - .map( - (e) => lat_lng.LatLng( - e.latitude!, - e.longitude!, - ), - ) - .whereNotNull() ?? - []; + } - final first = points.first; - if (first.intensityValue == null) { - return null; - } + Future _onEstimatedIntensityRegionChanged( + Map value, + ) async => + _eewEstimatedIntensityCalculatedRegionService?.update( + _EewEstimatedIntensityCalculatedRegionService.transform(value), + ); - final latLngs = [ - /* - ...points - .where((e) => first.intensityValue! < e.intensityValue! + 2) - .where((e) => e.intensityValue! > 1) - .map( - (e) => lat_lng.LatLng( - e.point.location.latitude, - e.point.location.longitude, - ), - ),*/ - ...coords.map( - (e) => lat_lng.LatLng( - e.lat + 3, - e.lon + 3, - ), - ), - ...coords.map( - (e) => lat_lng.LatLng( - e.lat - 3, - e.lon - 3, - ), - ), - ]; - return latLngs.toBounds; - } + Future _onEstimatedIntensityCityChanged( + Map value, + ) async => + _eewEstimatedIntensityCalculatedCityService?.update( + _EewEstimatedIntensityCalculatedCityService.transform(value), + ); // *********** Kyoshin Monitor Related *********** _KmoniObservationPointService? _kmoniObservationPointService; @@ -1321,6 +1335,194 @@ class _ShakeDetectionBorderService { static String get layerId => 'shake-detection-border'; } +class _EewEstimatedIntensityCalculatedRegionService { + _EewEstimatedIntensityCalculatedRegionService({required this.controller}) + : lock = Lock(); + + final MapLibreMapController controller; + final Lock lock; + + Future init(IntensityColorModel colorModel) async => lock.synchronized( + () async { + await dispose(); + await [ + // 各予想震度ごとにFill Layerを追加 + for (final intensity in JmaForecastIntensity.values) + controller.addLayer( + 'eqmonitor_map', + getFillLayerId(intensity), + FillLayerProperties( + fillColor: colorModel + .fromJmaForecastIntensity(intensity) + .background + .toHexStringRGB(), + ), + filter: [ + 'in', + ['get', 'code'], + [ + 'literal', + [], + ] + ], + sourceLayer: 'areaForecastLocalE', + belowLayerId: BaseLayer.areaForecastLocalELine.name, + ), + ].wait; + }, + timeout: const Duration(seconds: 5), + ); + + /// 予想震度を更新する + /// [areas] は Map{予想震度, 地域コード[]} + Future update(Map> areas) => [ + // 各予想震度ごとにFill Layerを追加 + for (final intensity in JmaForecastIntensity.values) + controller.setFilter( + getFillLayerId(intensity), + [ + 'in', + ['get', 'code'], + [ + 'literal', + areas[intensity] ?? [], + ] + ], + ), + ].wait; + + Future dispose() => [ + for (final intensity in JmaForecastIntensity.values) + controller.removeLayer(getFillLayerId(intensity)), + ].wait; + + Future onIntensityColorModelChanged(IntensityColorModel model) => + dispose().then( + (_) => init(model), + ); + + static Map> transform( + Map regions, + ) { + // Map<予想震度, List<地域コード>> に変換する + final regionsIntensityGrouped = >{}; + for (final entry in regions.entries) { + final intensity = JmaForecastIntensity.fromRealtimeIntensity(entry.value); + if (intensity == null) { + continue; + } + if (!regionsIntensityGrouped.containsKey(intensity)) { + regionsIntensityGrouped[intensity] = [entry.key]; + } else { + regionsIntensityGrouped[intensity]!.add(entry.key); + } + } + return regionsIntensityGrouped; + } + + static String getFillLayerId(JmaForecastIntensity intensity) { + final base = intensity.type + .replaceAll('-', 'low') + .replaceAll('+', 'high') + .replaceAll('不明', 'unknown'); + return '$_EewEstimatedIntensityCalculatedRegionService-fill-$base'; + } +} + +class _EewEstimatedIntensityCalculatedCityService { + _EewEstimatedIntensityCalculatedCityService({required this.controller}) + : lock = Lock(); + + final MapLibreMapController controller; + final Lock lock; + + Future init(IntensityColorModel colorModel) => lock.synchronized( + () async { + await dispose(); + await [ + // 各予想震度ごとにFill Layerを追加 + for (final intensity in JmaForecastIntensity.values) + controller.addLayer( + 'eqmonitor_map', + getFillLayerId(intensity), + FillLayerProperties( + fillColor: colorModel + .fromJmaForecastIntensity(intensity) + .background + .toHexStringRGB(), + ), + filter: [ + 'in', + ['get', 'regioncode'], + [ + 'literal', + [], + ] + ], + sourceLayer: 'areaInformationCityQuake', + belowLayerId: BaseLayer.areaForecastLocalELine.name, + ), + ].wait; + }, + timeout: const Duration(seconds: 5), + ); + + /// 予想震度を更新する + /// [areas] は Map{予想震度, 地域コード[]} + Future update(Map> areas) => [ + // 各予想震度ごとにFill Layerを追加 + for (final intensity in JmaForecastIntensity.values) + controller.setFilter( + getFillLayerId(intensity), + [ + 'in', + ['get', 'regioncode'], + [ + 'literal', + areas[intensity] ?? [], + ] + ], + ), + ].wait; + + Future dispose() => [ + for (final intensity in JmaForecastIntensity.values) + controller.removeLayer(getFillLayerId(intensity)), + ].wait; + + Future onIntensityColorModelChanged(IntensityColorModel model) => + dispose().then( + (_) => init(model), + ); + + static Map> transform( + Map regions, + ) { + // Map<予想震度, List<地域コード>> に変換する + final regionsIntensityGrouped = >{}; + for (final entry in regions.entries) { + final intensity = JmaForecastIntensity.fromRealtimeIntensity(entry.value); + if (intensity == null) { + continue; + } + if (!regionsIntensityGrouped.containsKey(intensity)) { + regionsIntensityGrouped[intensity] = [entry.key]; + } else { + regionsIntensityGrouped[intensity]!.add(entry.key); + } + } + return regionsIntensityGrouped; + } + + static String getFillLayerId(JmaForecastIntensity intensity) { + final base = intensity.type + .replaceAll('-', 'low') + .replaceAll('+', 'high') + .replaceAll('不明', 'unknown'); + return '$_EewEstimatedIntensityCalculatedCityService-fill-$base'; + } +} + @freezed class _EewHypocenterProperties with _$EewHypocenterProperties { const factory _EewHypocenterProperties({ diff --git a/app/lib/feature/home/features/map/viewmodel/main_map_viewmodel.g.dart b/app/lib/feature/home/features/map/viewmodel/main_map_viewmodel.g.dart index a3569c9c7..6962172cd 100644 --- a/app/lib/feature/home/features/map/viewmodel/main_map_viewmodel.g.dart +++ b/app/lib/feature/home/features/map/viewmodel/main_map_viewmodel.g.dart @@ -35,7 +35,7 @@ Map _$$_EewHypocenterPropertiesImplToJson( // RiverpodGenerator // ************************************************************************** -String _$mainMapViewModelHash() => r'd66f5fabfa955f959826038fc1c8ccd17c15fa17'; +String _$mainMapViewModelHash() => r'b74fbe895fc2ddf858b86a2e75f1c23a3d264122'; /// See also [MainMapViewModel]. @ProviderFor(MainMapViewModel) diff --git a/app/lib/feature/home/view/home_view.dart b/app/lib/feature/home/view/home_view.dart index 652f08fd9..e9a048303 100644 --- a/app/lib/feature/home/view/home_view.dart +++ b/app/lib/feature/home/view/home_view.dart @@ -268,6 +268,7 @@ class _HomeBodyWidget extends HookConsumerWidget { ref.listen( firebaseMessagingInteractionProvider, (_, next) async { + log('firebaseMessagingInteractionProvider: $next'); if (next case AsyncData(:final value)) { await WidgetsBinding.instance.endOfFrame.then((_) async { ref.read(talkerProvider).log( diff --git a/app/lib/feature/settings/children/config/debug/debugger_page.dart b/app/lib/feature/settings/children/config/debug/debugger_page.dart index 8065c6dec..9968b2ca3 100644 --- a/app/lib/feature/settings/children/config/debug/debugger_page.dart +++ b/app/lib/feature/settings/children/config/debug/debugger_page.dart @@ -4,6 +4,7 @@ import 'package:eqmonitor/core/provider/notification_token.dart'; import 'package:eqmonitor/core/provider/telegram_url/provider/telegram_url_provider.dart'; import 'package:eqmonitor/core/router/router.dart'; import 'package:eqmonitor/feature/home/component/sheet/sheet_header.dart'; +import 'package:eqmonitor/feature/home/features/eew_settings/eew_settings_notifier.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -35,6 +36,8 @@ class _DebugWidget extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); + final eewSettings = ref.watch(eewSettingsNotifierProvider); + return Card( margin: const EdgeInsets.all(4), elevation: 1, @@ -56,6 +59,20 @@ class _DebugWidget extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SheetHeader(title: 'デバッグメニュー'), + SwitchListTile.adaptive( + value: eewSettings.showCalculatedRegionIntensity, + onChanged: (value) => ref + .read(eewSettingsNotifierProvider.notifier) + .setShowCalculatedRegionIntensity(value: value), + title: const Text('距離減衰式による予想震度(Region)'), + ), + SwitchListTile.adaptive( + value: eewSettings.showCalculatedCityIntensity, + onChanged: (value) => ref + .read(eewSettingsNotifierProvider.notifier) + .setShowCalculatedCityIntensity(value: value), + title: const Text('距離減衰式による予想震度(City)'), + ), ListTile( title: const Text('ログ'), leading: const Icon(Icons.list), diff --git a/app/lib/feature/shake_detection/provider/shake_detection_provider.dart b/app/lib/feature/shake_detection/provider/shake_detection_provider.dart index c16821c2c..3504e9622 100644 --- a/app/lib/feature/shake_detection/provider/shake_detection_provider.dart +++ b/app/lib/feature/shake_detection/provider/shake_detection_provider.dart @@ -8,7 +8,6 @@ import 'package:eqmonitor/core/provider/time_ticker.dart'; import 'package:eqmonitor/core/provider/websocket/websocket_provider.dart'; import 'package:eqmonitor/feature/shake_detection/model/shake_detection_kmoni_merged_event.dart'; import 'package:extensions/extensions.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -54,9 +53,6 @@ class ShakeDetection extends _$ShakeDetection { /// 古くなったイベントを破棄 List _pruneOldEvents(List events) { - if (kDebugMode) { - return events; - } const duration = Duration(seconds: 30); return events .where( diff --git a/app/lib/feature/shake_detection/provider/shake_detection_provider.g.dart b/app/lib/feature/shake_detection/provider/shake_detection_provider.g.dart index 6c718f899..4d015d686 100644 --- a/app/lib/feature/shake_detection/provider/shake_detection_provider.g.dart +++ b/app/lib/feature/shake_detection/provider/shake_detection_provider.g.dart @@ -26,7 +26,7 @@ final _fetchShakeDetectionEventsProvider = typedef _FetchShakeDetectionEventsRef = FutureProviderRef>; -String _$shakeDetectionHash() => r'f2c69cc4d594c5e576a5075d52e695c4c51ba003'; +String _$shakeDetectionHash() => r'e5365b7a64a0befc468a794816fd999a4b9eb174'; /// See also [ShakeDetection]. @ProviderFor(ShakeDetection) diff --git a/app/pubspec.lock b/app/pubspec.lock index 34d3dbe4c..7981ee9c6 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1718,6 +1718,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: "direct main" + description: + name: synchronized + sha256: a824e842b8a054f91a728b783c177c1e4731f6b124f9192468457a8913371255 + url: "https://pub.dev" + source: hosted + version: "3.2.0" talker: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 86ba2c077..1d25e227d 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -80,6 +80,7 @@ dependencies: branch: main path: sheet shorebird_code_push: ^1.1.3 + synchronized: ^3.2.0 talker_dio_logger: ^4.4.1 talker_flutter: 4.4.0 upgrader: ^10.3.0 diff --git a/packages/eqapi_types/lib/src/model/core.dart b/packages/eqapi_types/lib/src/model/core.dart index 141009c47..0269e6747 100644 --- a/packages/eqapi_types/lib/src/model/core.dart +++ b/packages/eqapi_types/lib/src/model/core.dart @@ -94,6 +94,21 @@ enum JmaForecastIntensity { bool operator >=(JmaForecastIntensity other) { return index >= other.index; } + + static JmaForecastIntensity? fromRealtimeIntensity(double intensity) => + switch (intensity) { + < -0.5 => null, + < 0.5 => JmaForecastIntensity.zero, + < 1.5 => JmaForecastIntensity.one, + < 2.5 => JmaForecastIntensity.two, + < 3.5 => JmaForecastIntensity.three, + < 4.5 => JmaForecastIntensity.four, + < 5.0 => JmaForecastIntensity.fiveLower, + < 5.5 => JmaForecastIntensity.fiveUpper, + < 6.0 => JmaForecastIntensity.sixLower, + < 6.5 => JmaForecastIntensity.sixUpper, + _ => JmaForecastIntensity.seven, + }; } @JsonEnum(valueField: 'type') diff --git a/packages/jma_parameter_converter_internal/lib/converter/earthquake.dart b/packages/jma_parameter_converter_internal/lib/converter/earthquake.dart index 50bfce5d8..4176a7922 100644 --- a/packages/jma_parameter_converter_internal/lib/converter/earthquake.dart +++ b/packages/jma_parameter_converter_internal/lib/converter/earthquake.dart @@ -73,6 +73,7 @@ Future getArv({ // Cacheのチェック final cacheFile = File("cache/${latitude}_$longitude.json"); if (await cacheFile.exists()) { + print('Cache hit!: $cacheFile'); final json = jsonDecode(await cacheFile.readAsString()) as Map; final arvStr = (((json["features"] as List?)?.first @@ -81,7 +82,7 @@ Future getArv({ final arv = double.tryParse(arvStr.toString()); return arv; } - + print('Cache miss!: $cacheFile'); final response = await http.get( Uri.parse( 'https://www.j-shis.bosai.go.jp/map/api/sstrct/V2/meshinfo.geojson' diff --git a/packages/jma_parameter_converter_internal/updater.sh b/packages/jma_parameter_converter_internal/updater.sh index 085c03377..8e5fdd6fe 100755 --- a/packages/jma_parameter_converter_internal/updater.sh +++ b/packages/jma_parameter_converter_internal/updater.sh @@ -3,8 +3,8 @@ rm earthquake* 2> /dev/null rm tsunami* 2> /dev/null curl -qL "https://api.dmdata.jp/v2/parameter/earthquake/station" -H "authorization: Basic $(echo "$DMDATA_TOKEN": | base64)" > earthquake_parameter.json curl -qL "https://api.dmdata.jp/v2/parameter/tsunami/station" -H "authorization: Basic $(echo "$DMDATA_TOKEN": | base64)" > tsunami_parameter.json -dart run ./bin/jma_parameter_converter_internal.dart earthquake_parameter.json -dart run ./bin/jma_parameter_converter_internal.dart tsunami_parameter.json +fvm dart run ./bin/jma_parameter_converter_internal.dart earthquake_parameter.json +fvm dart run ./bin/jma_parameter_converter_internal.dart tsunami_parameter.json mv earthquake_parameter.buffer earthquake mv tsunami_parameter.buffer tsunami @@ -38,4 +38,4 @@ end wrangler r2 object put eqmonitor-prod/parameter/earthquake -f ./earthquake --ct application/octet-stream -wrangler r2 object put eqmonitor-prod/parameter/tsunami -f ./tsunami --ct application/octet-stream \ No newline at end of file +wrangler r2 object put eqmonitor-prod/parameter/tsunami -f ./tsunami --ct application/octet-stream