Skip to content

Commit

Permalink
fix: 距離減衰式の予想震度描画
Browse files Browse the repository at this point in the history
  • Loading branch information
YumNumm committed Sep 8, 2024
1 parent ed7b87a commit d3bf194
Show file tree
Hide file tree
Showing 22 changed files with 1,077 additions and 148 deletions.
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -14,26 +11,31 @@ EstimatedIntensityDataSource estimatedIntensityDataSource(
) =>
EstimatedIntensityDataSource();

typedef CalculationPoint = ({
double lat,
double lon,
double arv400,
});

class EstimatedIntensityDataSource {
List<AnalyzedKmoniObservationPoint> getEstimatedIntensity({
required List<KyoshinObservationPoint> points,
Iterable<double> getEstimatedIntensity({
required List<CalculationPoint> 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 = <AnalyzedKmoniObservationPoint>[];
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),
) -
Expand Down Expand Up @@ -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;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<AnalyzedKmoniObservationPoint> build() {
ref.listen(eewAliveTelegramProvider, (previous, next) {
log('EEW Telegram updated: $next');
state = calc(next ?? []);
Future<List<EstimatedIntensityPoint>> 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<AnalyzedKmoniObservationPoint> calc(List<EewV1> eews) {
final points = ref.read(kyoshinObservationPointsProvider);
final results = <List<AnalyzedKmoniObservationPoint>>[];
for (final eew in eews.where(
List<_CachedPoint>? _cachedPoints;
List<CalculationPoint>? _calculationPoints;

List<EstimatedIntensityPoint> calc(
List<EewV1> eews,
EarthquakeParameter parameter,
) {
// 計算前にPointを用意
_cachedPoints ??= _generateCachedPoints(parameter);
_calculationPoints ??= _generateCalculationPoints(_cachedPoints!);

final calculator = ref.read(estimatedIntensityDataSourceProvider);
final results = <List<double>>[];

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 = <EstimatedIntensityPoint>[];
// それぞれについて最大の値を取る
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<AnalyzedKmoniObservationPoint> _merge(
List<List<AnalyzedKmoniObservationPoint>> results,
Future<Iterable<EstimatedIntensityPoint>> calcInIsolate(
List<EewV1> eews,
EarthquakeParameter parameter,
) async =>
// TODO(YumNumm): 並列計算
calc(eews, parameter);

List<_CachedPoint> _generateCachedPoints(
EarthquakeParameter earthquake,
) {
final merged = <AnalyzedKmoniObservationPoint>[];
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<CalculationPoint> _generateCalculationPoints(
Iterable<_CachedPoint> points,
) {
final result = <CalculationPoint>[];
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<Map<String, double>> estimatedIntensityCity(
EstimatedIntensityCityRef ref,
) async* {
final estimatedIntensity = ref.watch(estimatedIntensityProvider).valueOrNull;
if (estimatedIntensity != null) {
final map = <String, double>{};
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<Map<String, double>> estimatedIntensityRegion(
EstimatedIntensityRegionRef ref,
) async* {
final estimatedIntensity = ref.watch(estimatedIntensityProvider).valueOrNull;
log(
'estimatedIntensityRegion: ${estimatedIntensity.runtimeType}, ${estimatedIntensity?.length}',
name: 'estimatedIntensityRegion',
);
if (estimatedIntensity != null) {
final map = <String, double>{};
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;
}
Loading

0 comments on commit d3bf194

Please sign in to comment.