Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions solvis/geojson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""A module providing geojson support functions."""
# flake8: noqa

import logging
from functools import lru_cache
from typing import Dict, Iterator, Optional, Tuple

import geopandas as gpd
import shapely
from nzshm_common.location import location_by_id

from solvis import geometry

log = logging.getLogger(__name__)


@lru_cache
def get_location_polygon(radius_km, lon, lat):
return geometry.circle_polygon(radius_m=radius_km * 1000, lon=lon, lat=lat)

Check warning on line 19 in solvis/geojson.py

View check run for this annotation

Codecov / codecov/patch

solvis/geojson.py#L19

Added line #L19 was not covered by tests


def location_features(locations: Tuple[str], radius_km: int, style: Optional[Dict]) -> Iterator[Dict]:

style = style or dict(

Check warning on line 24 in solvis/geojson.py

View check run for this annotation

Codecov / codecov/patch

solvis/geojson.py#L24

Added line #L24 was not covered by tests
stroke_color="lightblue", stroke_width=1, stroke_opacity=1.0, fill_color='lightblue', fill_opacity=0.7
)

for loc in locations:
log.debug(f'LOC {loc}')
item = location_by_id(loc)

Check warning on line 30 in solvis/geojson.py

View check run for this annotation

Codecov / codecov/patch

solvis/geojson.py#L29-L30

Added lines #L29 - L30 were not covered by tests
# polygon = solvis.circle_polygon(radius_km * 1000, lat=item.get('latitude'), lon=item.get('longitude'))
polygon = get_location_polygon(radius_km, lat=item.get('latitude'), lon=item.get('longitude'))
feature = dict(

Check warning on line 33 in solvis/geojson.py

View check run for this annotation

Codecov / codecov/patch

solvis/geojson.py#L32-L33

Added lines #L32 - L33 were not covered by tests
id=loc,
type="Feature",
geometry=shapely.geometry.mapping(polygon),
properties={
"title": item.get('name'),
"stroke-color": style.get('stroke_color'),
"stroke-opacity": style.get('stroke_opacity'),
"stroke-width": style.get('stroke_width'),
"fill-color": style.get('fill_color'),
"fill-opacity": style.get('fill_opacity'),
},
)
yield feature

Check warning on line 46 in solvis/geojson.py

View check run for this annotation

Codecov / codecov/patch

solvis/geojson.py#L46

Added line #L46 was not covered by tests


def location_features_geojson(locations: Tuple[str], radius_km: int, style: Optional[Dict] = None) -> Dict:
return dict(type="FeatureCollection", features=list(location_features(locations, radius_km, style)))

Check warning on line 50 in solvis/geojson.py

View check run for this annotation

Codecov / codecov/patch

solvis/geojson.py#L50

Added line #L50 was not covered by tests
92 changes: 66 additions & 26 deletions solvis/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import logging
import math
from functools import partial
from typing import Tuple, Union
from typing import List, Tuple, Union

import numpy as np
from pyproj import Transformer
Expand Down Expand Up @@ -140,6 +140,7 @@
Returns:
the fault surface coordinates
"""
# log.debug(f"fault_surface_3d dip_deg {dip_deg}")
return build_surface(trace, dip_dir, dip_deg, upper_depth, lower_depth, with_z_dimension=True)


Expand All @@ -160,6 +161,9 @@
surface: a Polygon object in 2D or 3D.
"""
trace = LineString(get_coordinates(trace))

# log.debug(f"build_surface dip_deg {dip_deg} with_z_dim: {with_z_dimension}")

depth = lower_depth - upper_depth
width = depth / math.tan(math.radians(dip_deg))
transformation = partial(translate_horizontally, dip_dir, width)
Expand Down Expand Up @@ -305,6 +309,17 @@
return dip_dir + 360 if dip_dir < 0 else dip_dir


def _polygon_from(lons: List[float], lats: List[float]):
# Add 360 to all negative longitudes
points = []
for i in range(len(lons)):
lon = lons[i]
if lon < 0:
lon += 360
points.append(Point(lon, lats[i]))
return Polygon(points)


def circle_polygon(radius_m: float, lat: float, lon: float) -> Polygon:
"""Creates a circular `Polygon` at a given radius in metres around the `lat, lon` coordinate.

Expand Down Expand Up @@ -351,62 +366,87 @@
# Get polygon with lat lon coordinates
transformer2 = Transformer.from_crs(local_azimuthal_projection, wgs84_projection)
lons, lats = transformer2.transform(*buffer.exterior.xy)

# Add 360 to all negative longitudes
points = []
for i in range(len(lons)):
lon = lons[i]
if lon < 0:
lon += 360
points.append(Point(lon, lats[i]))

return Polygon(points)
return _polygon_from(lons, lats)


def section_distance(
transformer: Transformer,
surface_geometry: Union[Polygon, LineString],
surface_geometry: LineString,
dip_dir: float,
dip_deg: float,
upper_depth: float,
lower_depth: float,
) -> float:
"""Calculate minimum distance from the transformer datum to a surface.

Where that surface is the surface projection of the fault.

WARNING: this function does not consider dip angle, assuming all faults to have dip = 90. For
now, use the surface projection method to approximate 3D distances.

Args:
transformer: typically from WGS84 to azimuthal
surface_geometry: the surface projection of the fault plane (`Polygon` or `LineString`)
upper_depth: the upper depth in km
upper_depth: the lower depth in km
lower_depth: the lower depth in km

Returns:
distance in meters

Raises:
ValueError: The `surface_geometry` was of an unsupported type.
"""
# print(f'trace coords: {surface_geometry.exterior.coords.xy}')
assert isinstance(surface_geometry, LineString), "Got an unhandled geometry type."

Check warning on line 399 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L399

Added line #L399 was not covered by tests
# assert 0
if isinstance(surface_geometry, Polygon):
trace = transformer.transform(*surface_geometry.exterior.coords.xy)
log.debug(f'Polygon coords: {surface_geometry.exterior.coords.xy}')
trace_transformed = transformer.transform(*surface_geometry.exterior.coords.xy)

Check warning on line 403 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L402-L403

Added lines #L402 - L403 were not covered by tests
# trace_transformed.append([lower_depth * -1000 for i in range(len(trace_transformed[0]))])
elif isinstance(surface_geometry, LineString):
trace = transformer.transform(*surface_geometry.coords.xy)
# log.debug(surface_geometry.coords)
log.debug(f'LineString coords: {surface_geometry.coords.xy}')
trace_transformed = transformer.transform(*surface_geometry.coords.xy)

Check warning on line 408 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L407-L408

Added lines #L407 - L408 were not covered by tests
else:
raise ValueError(f'unable to handle geometry: {surface_geometry}') # pragma: no cover

# print(f'trace offsets: {trace} (in metres relative to datum)')
origin = pv.PolyData([[0.0, 0.0, 0.0]], force_float=False)
surface = pv.PolyData(
[
[float(trace[0][0]), float(trace[1][0]), float(upper_depth * 1000)], # OK
[float(trace[0][1]), float(trace[1][1]), float(upper_depth * 1000)], # OK
[float(trace[0][0]), float(trace[1][0]), float(lower_depth * 1000)], # nope, but ok for basic test
[float(trace[0][1]), float(trace[1][1]), float(lower_depth * 1000)], # nope
]
)
log.debug(f'trace offsets: {trace_transformed} (in metres relative to datum)')

Check warning on line 412 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L412

Added line #L412 was not covered by tests

def linestring_from(lons: List[float], lats: List[float]):

Check warning on line 414 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L414

Added line #L414 was not covered by tests
# Add 360 to all negative longitudes
points = []

Check warning on line 416 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L416

Added line #L416 was not covered by tests
for i in range(len(lons)):
lon = lons[i]

Check warning on line 418 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L418

Added line #L418 was not covered by tests
if lon < 0:
lon += 360
points.append(Point(lon, lats[i]))
return LineString(points)

Check warning on line 422 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L420-L422

Added lines #L420 - L422 were not covered by tests

surface_transformed = linestring_from(*trace_transformed)

Check warning on line 424 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L424

Added line #L424 was not covered by tests

surface_polygon = fault_surface_3d(surface_transformed, dip_dir, dip_deg, upper_depth, lower_depth * -1000)

Check warning on line 426 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L426

Added line #L426 was not covered by tests

log.debug(f'surface_polygon: {surface_polygon}')

Check warning on line 428 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L428

Added line #L428 was not covered by tests

log.debug(f'surface_polygon: {get_coordinates(surface_polygon, include_z=True)}')

Check warning on line 430 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L430

Added line #L430 was not covered by tests

# assert 0
# zobj = zip(trace_transformed[0], trace_transformed[1], trace_transformed[2])
# assert 0
# trace = LineString(trace_transformed)
# surface = pv.PolyData(surface_polygon.exterior.coords.xy)
# # build_surface(trace, dip_dir, dip_deg, upper_depth, lower_depth, with_z_dimension=True)

origin = pv.PolyData([[0.0, 0.0, 0.0]], force_float=False)
surface = pv.PolyData(get_coordinates(surface_polygon, include_z=True))

Check warning on line 440 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L439-L440

Added lines #L439 - L440 were not covered by tests
closest_cells, closest_points = surface.find_closest_cell(
origin.points,
return_closest_point=True,
) # type: ignore[misc]

log.debug(f"closest_cells: {closest_cells}")
log.debug(f"closest_points: {closest_points}")

Check warning on line 447 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L446-L447

Added lines #L446 - L447 were not covered by tests

d_exact = np.linalg.norm(origin.points - closest_points, axis=1)

log.info(f"d_exact: {d_exact}")

Check warning on line 451 in solvis/geometry.py

View check run for this annotation

Codecov / codecov/patch

solvis/geometry.py#L451

Added line #L451 was not covered by tests
return d_exact[0] / 1000
Loading