kashima 1.0.0__tar.gz → 1.0.0.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kashima-1.0.0 → kashima-1.0.0.dev2}/PKG-INFO +3 -3
- {kashima-1.0.0 → kashima-1.0.0.dev2}/kashima.egg-info/PKG-INFO +3 -3
- kashima-1.0.0.dev2/kashima.egg-info/SOURCES.txt +7 -0
- kashima-1.0.0.dev2/kashima.egg-info/top_level.txt +1 -0
- {kashima-1.0.0 → kashima-1.0.0.dev2}/setup.py +2 -2
- kashima-1.0.0/dsra/__init__.py +0 -0
- kashima-1.0.0/gmdb/__init__.py +0 -0
- kashima-1.0.0/gmdp/__init__.py +0 -0
- kashima-1.0.0/gmpe/__init__.py +0 -0
- kashima-1.0.0/gmsp/__init__.py +0 -0
- kashima-1.0.0/kashima.egg-info/SOURCES.txt +0 -19
- kashima-1.0.0/kashima.egg-info/top_level.txt +0 -6
- kashima-1.0.0/mapper/__init__.py +0 -19
- kashima-1.0.0/mapper/base_map.py +0 -337
- kashima-1.0.0/mapper/blast_catalog.py +0 -111
- kashima-1.0.0/mapper/config.py +0 -158
- kashima-1.0.0/mapper/event_map.py +0 -536
- kashima-1.0.0/mapper/usgs_catalog.py +0 -209
- kashima-1.0.0/mapper/utils.py +0 -125
- {kashima-1.0.0 → kashima-1.0.0.dev2}/README.md +0 -0
- {kashima-1.0.0 → kashima-1.0.0.dev2}/kashima.egg-info/dependency_links.txt +0 -0
- {kashima-1.0.0 → kashima-1.0.0.dev2}/kashima.egg-info/requires.txt +0 -0
- {kashima-1.0.0 → kashima-1.0.0.dev2}/setup.cfg +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: kashima
|
|
3
|
-
Version: 1.0.0
|
|
4
|
-
Summary: Machine Learning Tools for
|
|
3
|
+
Version: 1.0.0.dev2
|
|
4
|
+
Summary: Machine Learning Tools for Geotechical Earthquake Engineering.
|
|
5
5
|
Home-page: https://github.com/averriK/kashima
|
|
6
6
|
Author: Alejandro Verri Kozlowski
|
|
7
7
|
Author-email: averri@fi.uba.ar
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: kashima
|
|
3
|
-
Version: 1.0.0
|
|
4
|
-
Summary: Machine Learning Tools for
|
|
3
|
+
Version: 1.0.0.dev2
|
|
4
|
+
Summary: Machine Learning Tools for Geotechical Earthquake Engineering.
|
|
5
5
|
Home-page: https://github.com/averriK/kashima
|
|
6
6
|
Author: Alejandro Verri Kozlowski
|
|
7
7
|
Author-email: averri@fi.uba.ar
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -2,10 +2,10 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name='kashima',
|
|
5
|
-
version='1.0.0',
|
|
5
|
+
version='1.0.0.dev2',
|
|
6
6
|
author='Alejandro Verri Kozlowski',
|
|
7
7
|
author_email='averri@fi.uba.ar',
|
|
8
|
-
description='Machine Learning Tools for
|
|
8
|
+
description='Machine Learning Tools for Geotechical Earthquake Engineering.',
|
|
9
9
|
long_description=open('README.md').read(),
|
|
10
10
|
long_description_content_type='text/markdown',
|
|
11
11
|
url='https://github.com/averriK/kashima', # Replace with your repository URL
|
kashima-1.0.0/dsra/__init__.py
DELETED
|
File without changes
|
kashima-1.0.0/gmdb/__init__.py
DELETED
|
File without changes
|
kashima-1.0.0/gmdp/__init__.py
DELETED
|
File without changes
|
kashima-1.0.0/gmpe/__init__.py
DELETED
|
File without changes
|
kashima-1.0.0/gmsp/__init__.py
DELETED
|
File without changes
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
README.md
|
|
2
|
-
setup.py
|
|
3
|
-
dsra/__init__.py
|
|
4
|
-
gmdb/__init__.py
|
|
5
|
-
gmdp/__init__.py
|
|
6
|
-
gmpe/__init__.py
|
|
7
|
-
gmsp/__init__.py
|
|
8
|
-
kashima.egg-info/PKG-INFO
|
|
9
|
-
kashima.egg-info/SOURCES.txt
|
|
10
|
-
kashima.egg-info/dependency_links.txt
|
|
11
|
-
kashima.egg-info/requires.txt
|
|
12
|
-
kashima.egg-info/top_level.txt
|
|
13
|
-
mapper/__init__.py
|
|
14
|
-
mapper/base_map.py
|
|
15
|
-
mapper/blast_catalog.py
|
|
16
|
-
mapper/config.py
|
|
17
|
-
mapper/event_map.py
|
|
18
|
-
mapper/usgs_catalog.py
|
|
19
|
-
mapper/utils.py
|
kashima-1.0.0/mapper/__init__.py
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from .config import MapConfig, EventConfig, FaultConfig, StationConfig, BlastConfig
|
|
2
|
-
from .utils import calculate_zoom_level, EARTH_RADIUS_KM
|
|
3
|
-
from .base_map import BaseMap
|
|
4
|
-
from .usgs_catalog import USGSCatalog
|
|
5
|
-
from .blast_catalog import BlastCatalog
|
|
6
|
-
from .event_map import EventMap
|
|
7
|
-
__all__ = [
|
|
8
|
-
'MapConfig',
|
|
9
|
-
'EventConfig',
|
|
10
|
-
'FaultConfig',
|
|
11
|
-
'StationConfig',
|
|
12
|
-
'BlastConfig',
|
|
13
|
-
'calculate_zoom_level',
|
|
14
|
-
'EARTH_RADIUS_KM',
|
|
15
|
-
'BaseMap',
|
|
16
|
-
'USGSCatalog',
|
|
17
|
-
'BlastCatalog',
|
|
18
|
-
'EventMap'
|
|
19
|
-
]
|
kashima-1.0.0/mapper/base_map.py
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import folium
|
|
2
|
-
import logging
|
|
3
|
-
import math
|
|
4
|
-
from .config import MapConfig, EventConfig, FaultConfig, StationConfig, TILE_LAYER_CONFIGS
|
|
5
|
-
from .utils import EARTH_RADIUS_KM
|
|
6
|
-
from pyproj import Transformer
|
|
7
|
-
from folium import plugins
|
|
8
|
-
|
|
9
|
-
logger = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
|
-
class BaseMap:
|
|
12
|
-
def __init__(
|
|
13
|
-
self,
|
|
14
|
-
map_config: MapConfig,
|
|
15
|
-
event_config: EventConfig = None,
|
|
16
|
-
fault_config: FaultConfig = None,
|
|
17
|
-
station_config: StationConfig = None,
|
|
18
|
-
log_level: int = logging.INFO
|
|
19
|
-
):
|
|
20
|
-
"""
|
|
21
|
-
Initialize the BaseMap object.
|
|
22
|
-
|
|
23
|
-
Parameters:
|
|
24
|
-
map_config (MapConfig): Configuration for the map.
|
|
25
|
-
event_config (EventConfig, optional): Configuration for seismic events.
|
|
26
|
-
fault_config (FaultConfig, optional): Configuration for fault lines.
|
|
27
|
-
station_config (StationConfig, optional): Configuration for seismic stations.
|
|
28
|
-
log_level (int, optional): Logging level. Defaults to logging.INFO.
|
|
29
|
-
"""
|
|
30
|
-
logger.setLevel(log_level)
|
|
31
|
-
# Validate that map_config is provided and is of correct type
|
|
32
|
-
if not isinstance(map_config, MapConfig):
|
|
33
|
-
logger.error("map_config must be an instance of MapConfig.")
|
|
34
|
-
raise TypeError("map_config must be an instance of MapConfig.")
|
|
35
|
-
self.map_config = map_config
|
|
36
|
-
|
|
37
|
-
# Validate event_config if provided
|
|
38
|
-
if event_config is not None and not isinstance(event_config, EventConfig):
|
|
39
|
-
logger.error("event_config must be an instance of EventConfig.")
|
|
40
|
-
raise TypeError("event_config must be an instance of EventConfig.")
|
|
41
|
-
self.event_config = event_config
|
|
42
|
-
|
|
43
|
-
# Validate fault_config if provided
|
|
44
|
-
if fault_config is not None and not isinstance(fault_config, FaultConfig):
|
|
45
|
-
logger.error("fault_config must be an instance of FaultConfig.")
|
|
46
|
-
raise TypeError("fault_config must be an instance of FaultConfig.")
|
|
47
|
-
self.fault_config = fault_config
|
|
48
|
-
|
|
49
|
-
# Validate station_config if provided
|
|
50
|
-
if station_config is not None and not isinstance(station_config, StationConfig):
|
|
51
|
-
logger.error("station_config must be an instance of StationConfig.")
|
|
52
|
-
raise TypeError("station_config must be an instance of StationConfig.")
|
|
53
|
-
self.station_config = station_config
|
|
54
|
-
|
|
55
|
-
self.events = None
|
|
56
|
-
self.color_map = None
|
|
57
|
-
self.m = None
|
|
58
|
-
self.stations = None
|
|
59
|
-
|
|
60
|
-
# Cap the radius to half the Earth's circumference (~20,037 km)
|
|
61
|
-
max_radius = EARTH_RADIUS_KM * math.pi
|
|
62
|
-
if self.map_config.radius_km > max_radius:
|
|
63
|
-
logger.warning(f"radius_km exceeds half the Earth's circumference. Capping to {max_radius} km.")
|
|
64
|
-
self.map_config.radius_km = max_radius
|
|
65
|
-
|
|
66
|
-
# Ensure radius_km is positive
|
|
67
|
-
if self.map_config.radius_km <= 0:
|
|
68
|
-
logger.error("radius_km must be a positive value.")
|
|
69
|
-
raise ValueError("radius_km must be a positive value.")
|
|
70
|
-
|
|
71
|
-
def initialize_map(self):
|
|
72
|
-
"""Initialize the folium map object."""
|
|
73
|
-
logger.info("Initializing folium map...")
|
|
74
|
-
|
|
75
|
-
self.m = folium.Map(
|
|
76
|
-
location=[self.map_config.latitude, self.map_config.longitude],
|
|
77
|
-
zoom_start=self.map_config.base_zoom_level,
|
|
78
|
-
min_zoom=self.map_config.min_zoom_level,
|
|
79
|
-
max_zoom=self.map_config.max_zoom_level,
|
|
80
|
-
tiles=None # Prevents adding the default OpenStreetMap layer
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
# Add tile layers
|
|
84
|
-
self.add_tile_layers()
|
|
85
|
-
|
|
86
|
-
logger.info("Folium map initialized.")
|
|
87
|
-
|
|
88
|
-
def add_tooltip_css(self) -> None:
|
|
89
|
-
"""Add custom CSS for tooltip styling."""
|
|
90
|
-
tooltip_style = """
|
|
91
|
-
<style>
|
|
92
|
-
.folium-tooltip {
|
|
93
|
-
width: 200px;
|
|
94
|
-
white-space: normal;
|
|
95
|
-
}
|
|
96
|
-
</style>
|
|
97
|
-
"""
|
|
98
|
-
self.m.get_root().header.add_child(folium.Element(tooltip_style))
|
|
99
|
-
|
|
100
|
-
def set_map_bounds(self):
|
|
101
|
-
"""Set the map bounds based on the site location and radius."""
|
|
102
|
-
logger.info("Setting map bounds...")
|
|
103
|
-
bounds = self.get_combined_bounds()
|
|
104
|
-
self.m.fit_bounds(bounds)
|
|
105
|
-
logger.info("Map bounds set.")
|
|
106
|
-
|
|
107
|
-
def get_combined_bounds(self):
|
|
108
|
-
"""Calculate and return the combined bounds for the map."""
|
|
109
|
-
logger.info("Calculating combined bounds...")
|
|
110
|
-
|
|
111
|
-
# Start with the bounding box around the site location
|
|
112
|
-
site_bounds = self.get_bounding_box()
|
|
113
|
-
|
|
114
|
-
# Initialize lists to collect all coordinates
|
|
115
|
-
all_lats = [site_bounds[0][0], site_bounds[1][0]] # min_lat, max_lat
|
|
116
|
-
all_lons = [site_bounds[0][1], site_bounds[1][1]] # min_lon, max_lon
|
|
117
|
-
|
|
118
|
-
# Include event coordinates if available
|
|
119
|
-
if self.events is not None:
|
|
120
|
-
all_lats.extend(self.events['latitude'].tolist())
|
|
121
|
-
all_lons.extend(self.events['longitude'].tolist())
|
|
122
|
-
|
|
123
|
-
# Include station coordinates if available
|
|
124
|
-
if self.stations is not None:
|
|
125
|
-
all_lats.extend(self.stations['latitude'].tolist())
|
|
126
|
-
all_lons.extend(self.stations['longitude'].tolist())
|
|
127
|
-
|
|
128
|
-
# Compute the min and max for latitude and longitude
|
|
129
|
-
min_lat, max_lat = min(all_lats), max(all_lats)
|
|
130
|
-
min_lon, max_lon = min(all_lons), max(all_lons)
|
|
131
|
-
|
|
132
|
-
bounds = [[min_lat, min_lon], [max_lat, max_lon]]
|
|
133
|
-
logger.info(f"Calculated map bounds: {bounds}")
|
|
134
|
-
return bounds
|
|
135
|
-
|
|
136
|
-
def get_bounding_box(self, padding_factor=1.1):
|
|
137
|
-
"""Calculate the bounding box for a given point and radius with padding."""
|
|
138
|
-
lat_rad = math.radians(self.map_config.latitude)
|
|
139
|
-
lon_rad = math.radians(self.map_config.longitude)
|
|
140
|
-
angular_distance = (self.map_config.radius_km * padding_factor) / EARTH_RADIUS_KM
|
|
141
|
-
|
|
142
|
-
# Limit angular_distance to pi radians
|
|
143
|
-
angular_distance = min(angular_distance, math.pi)
|
|
144
|
-
|
|
145
|
-
min_lat = math.degrees(lat_rad - angular_distance)
|
|
146
|
-
max_lat = math.degrees(lat_rad + angular_distance)
|
|
147
|
-
|
|
148
|
-
# Ensure latitude bounds are within -90 and 90 degrees
|
|
149
|
-
min_lat = max(min_lat, -90)
|
|
150
|
-
max_lat = min(max_lat, 90)
|
|
151
|
-
|
|
152
|
-
cos_lat_rad = math.cos(lat_rad)
|
|
153
|
-
|
|
154
|
-
# Compute delta_lon with clamping to avoid math domain error
|
|
155
|
-
if abs(cos_lat_rad) < 1e-10:
|
|
156
|
-
# At the poles, longitude bounds are irrelevant
|
|
157
|
-
min_lon = -180
|
|
158
|
-
max_lon = 180
|
|
159
|
-
else:
|
|
160
|
-
sin_angular_distance = math.sin(angular_distance)
|
|
161
|
-
ratio = sin_angular_distance / cos_lat_rad
|
|
162
|
-
|
|
163
|
-
# Clamp the ratio between -1 and 1 to avoid math domain error
|
|
164
|
-
ratio = max(-1, min(1, ratio))
|
|
165
|
-
|
|
166
|
-
delta_lon = math.asin(ratio)
|
|
167
|
-
min_lon = math.degrees(lon_rad - delta_lon)
|
|
168
|
-
max_lon = math.degrees(lon_rad + delta_lon)
|
|
169
|
-
|
|
170
|
-
# Normalize longitude to be within -180 to 180 degrees
|
|
171
|
-
min_lon = (min_lon + 180) % 360 - 180
|
|
172
|
-
max_lon = (max_lon + 180) % 360 - 180
|
|
173
|
-
|
|
174
|
-
return [[min_lat, min_lon], [max_lat, max_lon]]
|
|
175
|
-
|
|
176
|
-
def create_marker_group(self, name='Events'):
|
|
177
|
-
"""Create a feature group for markers."""
|
|
178
|
-
logger.info(f"Creating marker feature group: {name}...")
|
|
179
|
-
self.marker_group = folium.FeatureGroup(name=name)
|
|
180
|
-
|
|
181
|
-
def convert_xy_to_latlon(self, df, x_col='x', y_col='y', source_crs='EPSG:4326'):
|
|
182
|
-
"""
|
|
183
|
-
Convert XY coordinates in a given DataFrame to latitude/longitude (WGS84).
|
|
184
|
-
|
|
185
|
-
Parameters
|
|
186
|
-
----------
|
|
187
|
-
df : pd.DataFrame
|
|
188
|
-
The DataFrame containing columns for X and Y coordinates.
|
|
189
|
-
x_col : str, optional
|
|
190
|
-
The column name of the X (easting) coordinate. Default is 'x'.
|
|
191
|
-
y_col : str, optional
|
|
192
|
-
The column name of the Y (northing) coordinate. Default is 'y'.
|
|
193
|
-
source_crs : str, optional
|
|
194
|
-
The coordinate reference system (CRS) of the input coordinates, e.g., "EPSG:31982".
|
|
195
|
-
Default is 'EPSG:4326'.
|
|
196
|
-
|
|
197
|
-
Returns
|
|
198
|
-
-------
|
|
199
|
-
pd.DataFrame
|
|
200
|
-
The original DataFrame with two new columns: 'latitude' and 'longitude'.
|
|
201
|
-
|
|
202
|
-
Raises
|
|
203
|
-
------
|
|
204
|
-
ValueError
|
|
205
|
-
If transformation fails or input columns are missing.
|
|
206
|
-
"""
|
|
207
|
-
logger.info("Converting XY coordinates to latitude/longitude...")
|
|
208
|
-
if x_col not in df.columns or y_col not in df.columns:
|
|
209
|
-
logger.error(f"DataFrame must contain '{x_col}' and '{y_col}' columns.")
|
|
210
|
-
raise ValueError(f"DataFrame must contain '{x_col}' and '{y_col}' columns.")
|
|
211
|
-
|
|
212
|
-
try:
|
|
213
|
-
transformer = Transformer.from_crs(
|
|
214
|
-
source_crs, # Source CRS provided
|
|
215
|
-
'EPSG:4326', # Target CRS (WGS84)
|
|
216
|
-
always_xy=True
|
|
217
|
-
)
|
|
218
|
-
x_coords = df[x_col].values
|
|
219
|
-
y_coords = df[y_col].values
|
|
220
|
-
lon, lat = transformer.transform(x_coords, y_coords)
|
|
221
|
-
df['latitude'] = lat
|
|
222
|
-
df['longitude'] = lon
|
|
223
|
-
logger.info("Coordinate conversion complete.")
|
|
224
|
-
return df
|
|
225
|
-
except Exception as e:
|
|
226
|
-
logger.error(f"Error converting station coordinates: {e}")
|
|
227
|
-
raise ValueError(f"Error converting coordinates: {e}")
|
|
228
|
-
|
|
229
|
-
def assign_color(self, value):
|
|
230
|
-
"""Assign color based on magnitude value."""
|
|
231
|
-
return 'lightgrey' if value < self.event_config.vmin else self.color_map(value)
|
|
232
|
-
|
|
233
|
-
def calculate_radius(self, value):
|
|
234
|
-
"""Calculate the radius of the circle marker based on a value."""
|
|
235
|
-
base_radius = 2
|
|
236
|
-
return base_radius + (value - self.event_config.vmin) * self.event_config.scaling_factor
|
|
237
|
-
|
|
238
|
-
def add_tile_layers(self):
|
|
239
|
-
"""Add all tile layers to the map, ensuring the specified layer is the default."""
|
|
240
|
-
logger.info("Adding tile layers...")
|
|
241
|
-
|
|
242
|
-
# Add all layers except the default one
|
|
243
|
-
for layer_name, config in TILE_LAYER_CONFIGS.items():
|
|
244
|
-
if layer_name != self.map_config.default_tile_layer:
|
|
245
|
-
folium.TileLayer(
|
|
246
|
-
layer_name,
|
|
247
|
-
name=layer_name,
|
|
248
|
-
attr=config['attr'],
|
|
249
|
-
control=True,
|
|
250
|
-
max_zoom=self.map_config.max_zoom_level,
|
|
251
|
-
min_zoom=self.map_config.min_zoom_level
|
|
252
|
-
).add_to(self.m)
|
|
253
|
-
|
|
254
|
-
# Add the default layer last
|
|
255
|
-
default_config = TILE_LAYER_CONFIGS[self.map_config.default_tile_layer]
|
|
256
|
-
folium.TileLayer(
|
|
257
|
-
self.map_config.default_tile_layer,
|
|
258
|
-
name=self.map_config.default_tile_layer,
|
|
259
|
-
attr=default_config['attr'],
|
|
260
|
-
control=True,
|
|
261
|
-
max_zoom=self.map_config.max_zoom_level,
|
|
262
|
-
min_zoom=self.map_config.min_zoom_level
|
|
263
|
-
).add_to(self.m)
|
|
264
|
-
|
|
265
|
-
logger.info("Tile layers added.")
|
|
266
|
-
|
|
267
|
-
def add_layer_controls(self):
|
|
268
|
-
"""Add layer controls to the map."""
|
|
269
|
-
logger.info("Adding layer control...")
|
|
270
|
-
folium.LayerControl().add_to(self.m)
|
|
271
|
-
logger.info("Layer control added.")
|
|
272
|
-
|
|
273
|
-
def add_fullscreen_option(self):
|
|
274
|
-
"""Add a fullscreen button to the map."""
|
|
275
|
-
logger.info("Adding fullscreen option...")
|
|
276
|
-
plugins.Fullscreen(
|
|
277
|
-
position='topleft',
|
|
278
|
-
title='Full Screen',
|
|
279
|
-
title_cancel='Exit Full Screen',
|
|
280
|
-
force_separate_button=True
|
|
281
|
-
).add_to(self.m)
|
|
282
|
-
logger.info("Fullscreen option added.")
|
|
283
|
-
|
|
284
|
-
def add_legends(self) -> None:
|
|
285
|
-
"""Add legends to the map."""
|
|
286
|
-
if self.event_config is None:
|
|
287
|
-
logger.info("Event configuration is missing. Skipping legends.")
|
|
288
|
-
return
|
|
289
|
-
|
|
290
|
-
# Add the magnitude color legend using branca colormap
|
|
291
|
-
logger.info("Adding magnitude color legend...")
|
|
292
|
-
if hasattr(self, 'color_map') and self.color_map:
|
|
293
|
-
self.color_map.caption = self.event_config.legend_title
|
|
294
|
-
self.color_map.position = self.get_color_map_position()
|
|
295
|
-
self.color_map.add_to(self.m)
|
|
296
|
-
logger.info("Magnitude color legend added.")
|
|
297
|
-
else:
|
|
298
|
-
logger.warning("Color map not found. Magnitude color legend not added.")
|
|
299
|
-
|
|
300
|
-
def add_site_marker(self):
|
|
301
|
-
"""Add a marker for the site location on the map."""
|
|
302
|
-
logger.info("Adding site location marker...")
|
|
303
|
-
# Create the tooltip and popup content
|
|
304
|
-
tooltip_content = self.map_config.project_name
|
|
305
|
-
popup_content = f"""
|
|
306
|
-
<b>Site Project:</b> {self.map_config.project_name}<br>
|
|
307
|
-
<b>Client:</b> {self.map_config.client}
|
|
308
|
-
"""
|
|
309
|
-
|
|
310
|
-
# Add the marker with star icon, tooltip, and popup
|
|
311
|
-
folium.Marker(
|
|
312
|
-
location=[self.map_config.latitude, self.map_config.longitude],
|
|
313
|
-
icon=folium.Icon(color='red', icon='star', prefix='fa'),
|
|
314
|
-
tooltip=tooltip_content,
|
|
315
|
-
popup=folium.Popup(popup_content, max_width=300)
|
|
316
|
-
).add_to(self.m)
|
|
317
|
-
logger.info("Site location marker added.")
|
|
318
|
-
|
|
319
|
-
def get_position_style(self, position):
|
|
320
|
-
positions = {
|
|
321
|
-
'topright': 'top: 10px; right: 10px;',
|
|
322
|
-
'topleft': 'top: 10px; left: 10px;',
|
|
323
|
-
'bottomright': 'bottom: 10px; right: 10px;',
|
|
324
|
-
'bottomleft': 'bottom: 10px; left: 10px;'
|
|
325
|
-
}
|
|
326
|
-
return positions.get(position.lower(), 'top: 10px; right: 10px;')
|
|
327
|
-
|
|
328
|
-
def get_color_map_position(self) -> str:
|
|
329
|
-
"""Get the position for the color map legend."""
|
|
330
|
-
position = self.event_config.legend_position.lower()
|
|
331
|
-
positions = {
|
|
332
|
-
'topright': 'topright',
|
|
333
|
-
'topleft': 'topleft',
|
|
334
|
-
'bottomright': 'bottomright',
|
|
335
|
-
'bottomleft': 'bottomleft',
|
|
336
|
-
}
|
|
337
|
-
return positions.get(position, 'bottomright')
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import pandas as pd
|
|
3
|
-
from .config import BlastConfig
|
|
4
|
-
from .utils import convert_xy_to_latlon, calculate_magnitude # Import functions from utils
|
|
5
|
-
|
|
6
|
-
logger = logging.getLogger(__name__)
|
|
7
|
-
logging.basicConfig(level=logging.INFO)
|
|
8
|
-
|
|
9
|
-
class BlastCatalog:
|
|
10
|
-
"""Class to process blast data and build a catalog."""
|
|
11
|
-
def __init__(self, blast_config: BlastConfig):
|
|
12
|
-
self.blast_config = blast_config
|
|
13
|
-
self.dataframe = None
|
|
14
|
-
|
|
15
|
-
def read_blast_data(self):
|
|
16
|
-
"""Read blast data from CSV file."""
|
|
17
|
-
try:
|
|
18
|
-
self.dataframe = pd.read_csv(self.blast_config.blast_file_path)
|
|
19
|
-
logger.info(f"Blast data loaded from {self.blast_config.blast_file_path}")
|
|
20
|
-
except Exception as e:
|
|
21
|
-
logger.error(f"Failed to read blast data: {e}")
|
|
22
|
-
self.dataframe = pd.DataFrame()
|
|
23
|
-
|
|
24
|
-
def build_catalog(self):
|
|
25
|
-
"""Build catalog DataFrame with required columns."""
|
|
26
|
-
if self.dataframe is None or self.dataframe.empty:
|
|
27
|
-
logger.error("No data to build catalog.")
|
|
28
|
-
return pd.DataFrame()
|
|
29
|
-
|
|
30
|
-
# Check for essential columns x, y, Q
|
|
31
|
-
essential_columns = ['x', 'y', 'Q']
|
|
32
|
-
missing_essentials = [col for col in essential_columns if col not in self.dataframe.columns]
|
|
33
|
-
if missing_essentials:
|
|
34
|
-
logger.error(f"Missing essential columns: {missing_essentials}")
|
|
35
|
-
return pd.DataFrame()
|
|
36
|
-
|
|
37
|
-
# Convert coordinates if not already done
|
|
38
|
-
if 'latitude' not in self.dataframe.columns or 'longitude' not in self.dataframe.columns:
|
|
39
|
-
self.process_data()
|
|
40
|
-
|
|
41
|
-
# Calculate magnitude if not already done
|
|
42
|
-
if 'mag' not in self.dataframe.columns:
|
|
43
|
-
self.calculate_magnitude()
|
|
44
|
-
|
|
45
|
-
# Add or modify the 'magType' column
|
|
46
|
-
self.dataframe['magType'] = 'ML'
|
|
47
|
-
|
|
48
|
-
# Handle 'depth' column; if not present, create with default value 0
|
|
49
|
-
if 'depth' not in self.dataframe.columns:
|
|
50
|
-
if 'z' in self.dataframe.columns:
|
|
51
|
-
self.dataframe['depth'] = self.dataframe['z']
|
|
52
|
-
else:
|
|
53
|
-
self.dataframe['depth'] = 0
|
|
54
|
-
|
|
55
|
-
# Handle 'time' column; if not present, create with an arbitrary date
|
|
56
|
-
if 'date' in self.dataframe.columns:
|
|
57
|
-
self.dataframe['time'] = pd.to_datetime(self.dataframe['date'], format='%d/%m/%Y', errors='coerce')
|
|
58
|
-
else:
|
|
59
|
-
# Assign arbitrary date '1970-01-01'
|
|
60
|
-
self.dataframe['time'] = pd.Timestamp('1970-01-01')
|
|
61
|
-
|
|
62
|
-
# Handle 'place' column; if not present, assign 'Unknown'
|
|
63
|
-
self.dataframe['place'] = self.dataframe.get('place', 'Unknown')
|
|
64
|
-
|
|
65
|
-
# Handle 'id' column; if not present, assign sequence numbers
|
|
66
|
-
if 'id' not in self.dataframe.columns:
|
|
67
|
-
self.dataframe['id'] = range(1, len(self.dataframe) + 1)
|
|
68
|
-
|
|
69
|
-
# Select required columns
|
|
70
|
-
required_columns = ['latitude', 'longitude', 'mag', 'magType', 'depth', 'id', 'time', 'place']
|
|
71
|
-
catalog = self.dataframe[required_columns].copy()
|
|
72
|
-
|
|
73
|
-
# Drop rows with missing essential values
|
|
74
|
-
catalog.dropna(subset=['latitude', 'longitude', 'mag'], inplace=True)
|
|
75
|
-
|
|
76
|
-
logger.info(f"Catalog built with {len(catalog)} events.")
|
|
77
|
-
return catalog
|
|
78
|
-
|
|
79
|
-
def process_data(self):
|
|
80
|
-
"""Process the blast data by converting coordinates and calculating magnitude."""
|
|
81
|
-
if self.dataframe is None or self.dataframe.empty:
|
|
82
|
-
logger.error("No data to process.")
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
# Convert coordinates using the utility function
|
|
86
|
-
x_coords = self.dataframe['x'].values
|
|
87
|
-
y_coords = self.dataframe['y'].values
|
|
88
|
-
|
|
89
|
-
lon, lat = convert_xy_to_latlon(
|
|
90
|
-
x_coords,
|
|
91
|
-
y_coords,
|
|
92
|
-
source_crs=self.blast_config.coordinate_system
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
self.dataframe['longitude'] = lon
|
|
96
|
-
self.dataframe['latitude'] = lat
|
|
97
|
-
|
|
98
|
-
# Calculate magnitude using the function from utils
|
|
99
|
-
Q_values = self.dataframe['Q'].values
|
|
100
|
-
|
|
101
|
-
mag = calculate_magnitude(
|
|
102
|
-
Q=Q_values,
|
|
103
|
-
f_TNT=self.blast_config.f_TNT,
|
|
104
|
-
a_ML=self.blast_config.a_ML,
|
|
105
|
-
b_ML=self.blast_config.b_ML
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
self.dataframe['mag'] = mag
|
|
109
|
-
logger.info("Magnitude calculation completed.")
|
|
110
|
-
|
|
111
|
-
|