# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, print_function,
unicode_literals)
from future.builtins import * # NOQA
from future.utils import native_str
from obspy import Catalog
from obspy.core.event import Origin, Magnitude
from obspy.core.inventory import Inventory
from obspy.core.util import to_int_or_zero
try:
import gdal
from osgeo import ogr, osr
from osgeo.gdal import __version__ as __gdal_version__
except ImportError as e:
HAS_GDAL = False
GDAL_VERSION_SUFFICIENT = False
IMPORTERROR_MSG = str(e) + (
". ObsPy's write support for shapefiles requires the 'gdal' module "
"to be installed in addition to the general ObsPy dependencies.")
GDAL_TOO_OLD_MSG = str(e) + (
". ObsPy's write support for shapefiles requires the 'gdal' module "
"to be installed in a more recent version.")
else:
HAS_GDAL = True
GDAL_VERSION_SUFFICIENT = [1, 7, 3] < list(map(
to_int_or_zero, __gdal_version__.split(".")))
gdal.UseExceptions()
[docs]def _write_shapefile(obj, filename, **kwargs):
"""
Write :class:`~obspy.core.inventory.inventory.Inventory` or
:class:`~obspy.core.event.Catalog` object to a ESRI shapefile.
:type obj: :class:`~obspy.core.event.Catalog` or
:class:`~obspy.core.inventory.Inventory`
:param obj: ObsPy object for shapefile output
:type filename: str
:param filename: Filename to write to. According to ESRI shapefile
definition, multiple files with the following suffixes will be written:
".shp", ".shx", ".dbj", ".prj". If filename does not end with ".shp",
it will be appended. Other files will be created with respective
suffixes accordingly.
"""
if not HAS_GDAL:
raise ImportError(IMPORTERROR_MSG)
if not GDAL_VERSION_SUFFICIENT:
raise ImportError(GDAL_TOO_OLD_MSG)
if not filename.endswith(".shp"):
filename += ".shp"
driver = ogr.GetDriverByName(native_str("ESRI Shapefile"))
driver.DeleteDataSource(filename)
data_source = driver.CreateDataSource(filename)
try:
# create the layer
if isinstance(obj, Catalog):
_add_catalog_layer(data_source, obj)
elif isinstance(obj, Inventory):
_add_inventory_layer(data_source, obj)
else:
msg = ("Object for shapefile output must be "
"a Catalog or Inventory.")
raise TypeError(msg)
finally:
# Destroy the data source to free resources
data_source.Destroy()
[docs]def _add_catalog_layer(data_source, catalog):
"""
:type data_source: :class:`osgeo.ogr.DataSource`.
:param data_source: OGR data source the layer is added to.
:type catalog: :class:`~obspy.core.event.Catalog`
:param catalog: Event data to add as a new layer.
"""
if not HAS_GDAL:
raise ImportError(IMPORTERROR_MSG)
if not GDAL_VERSION_SUFFICIENT:
raise ImportError(GDAL_TOO_OLD_MSG)
# [name, type, width, precision]
# field name is 10 chars max
# ESRI shapefile attributes are stored in dbf files, which can not
# store datetimes, only dates, see:
# http://www.gdal.org/drv_shapefile.html
# use POSIX timestamp for exact origin time, set time of first pick
# for events with no origin
field_definitions = [
["EventID", ogr.OFTString, 100, None],
["OriginID", ogr.OFTString, 100, None],
["MagID", ogr.OFTString, 100, None],
["Date", ogr.OFTDate, None, None],
["OriginTime", ogr.OFTReal, 20, 6],
["FirstPick", ogr.OFTReal, 20, 6],
["Longitude", ogr.OFTReal, 16, 10],
["Latitude", ogr.OFTReal, 16, 10],
["Depth", ogr.OFTReal, 8, 3],
["Magnitude", ogr.OFTReal, 8, 3],
]
layer = _create_layer(data_source, "earthquakes", field_definitions)
layer_definition = layer.GetLayerDefn()
for event in catalog:
# try to use preferred origin/magnitude, fall back to first or use
# empty one with `None` values in it
origin = (event.preferred_origin() or
event.origins and event.origins[0] or
Origin(force_resource_id=False))
magnitude = (event.preferred_magnitude() or
event.magnitudes and event.magnitudes[0] or
Magnitude(force_resource_id=False))
t_origin = origin.time
pick_times = [pick.time for pick in event.picks
if pick.time is not None]
t_pick = pick_times and min(pick_times) or None
date = t_origin or t_pick
feature = ogr.Feature(layer_definition)
try:
# setting fields with `None` results in values of `0.000`
# need to really omit setting values if they are `None`
if event.resource_id is not None:
feature.SetField(native_str("EventID"),
native_str(event.resource_id))
if origin.resource_id is not None:
feature.SetField(native_str("OriginID"),
native_str(origin.resource_id))
if t_origin is not None:
# Use timestamp for exact timing
feature.SetField(native_str("OriginTime"), t_origin.timestamp)
if t_pick is not None:
# Use timestamp for exact timing
feature.SetField(native_str("FirstPick"), t_pick.timestamp)
if date is not None:
# ESRI shapefile attributes are stored in dbf files, which can
# not store datetimes, only dates. We still need to use the
# GDAL API with precision up to seconds (aiming at other output
# drivers of GDAL; `100` stands for GMT)
feature.SetField(native_str("Date"), date.year, date.month,
date.day, date.hour, date.minute, date.second,
100)
if origin.latitude is not None:
feature.SetField(native_str("Latitude"), origin.latitude)
if origin.longitude is not None:
feature.SetField(native_str("Longitude"), origin.longitude)
if origin.depth is not None:
feature.SetField(native_str("Depth"), origin.depth / 1e3)
if magnitude.mag is not None:
feature.SetField(native_str("Magnitude"), magnitude.mag)
if magnitude.resource_id is not None:
feature.SetField(native_str("MagID"),
native_str(magnitude.resource_id))
if origin.latitude is not None and origin.longitude is not None:
point = ogr.Geometry(ogr.wkbPoint)
point.AddPoint(origin.longitude, origin.latitude)
feature.SetGeometry(point)
layer.CreateFeature(feature)
finally:
# Destroy the feature to free resources
feature.Destroy()
[docs]def _add_inventory_layer(data_source, inventory):
"""
:type data_source: :class:`osgeo.ogr.DataSource`.
:param data_source: OGR data source the layer is added to.
:type inventory: :class:`~obspy.core.inventory.Inventory`
:param inventory: Inventory data to add as a new layer.
"""
if not HAS_GDAL:
raise ImportError(IMPORTERROR_MSG)
if not GDAL_VERSION_SUFFICIENT:
raise ImportError(GDAL_TOO_OLD_MSG)
# [name, type, width, precision]
# field name is 10 chars max
# ESRI shapefile attributes are stored in dbf files, which can not
# store datetimes, only dates, see:
# http://www.gdal.org/drv_shapefile.html
# use POSIX timestamp for exact origin time, set time of first pick
# for events with no origin
field_definitions = [
["Network", ogr.OFTString, 20, None],
["Station", ogr.OFTString, 20, None],
["Longitude", ogr.OFTReal, 16, 10],
["Latitude", ogr.OFTReal, 16, 10],
["Elevation", ogr.OFTReal, 9, 3],
["StartDate", ogr.OFTDate, None, None],
["EndDate", ogr.OFTDate, None, None],
["Channels", ogr.OFTString, 254, None],
]
layer = _create_layer(data_source, "stations", field_definitions)
layer_definition = layer.GetLayerDefn()
for net in inventory:
for sta in net:
channel_list = ",".join(["%s.%s" % (cha.location_code, cha.code)
for cha in sta])
feature = ogr.Feature(layer_definition)
try:
# setting fields with `None` results in values of `0.000`
# need to really omit setting values if they are `None`
if net.code is not None:
feature.SetField(native_str("Network"),
native_str(net.code))
if sta.code is not None:
feature.SetField(native_str("Station"),
native_str(sta.code))
if sta.latitude is not None:
feature.SetField(native_str("Latitude"), sta.latitude)
if sta.longitude is not None:
feature.SetField(native_str("Longitude"), sta.longitude)
if sta.elevation is not None:
feature.SetField(native_str("Elevation"), sta.elevation)
if sta.start_date is not None:
date = sta.start_date
# ESRI shapefile attributes are stored in dbf files, which
# can not store datetimes, only dates. We still need to use
# the GDAL API with precision up to seconds (aiming at
# other output drivers of GDAL; `100` stands for GMT)
feature.SetField(native_str("StartDate"), date.year,
date.month, date.day, date.hour,
date.minute, date.second, 100)
if sta.end_date is not None:
date = sta.end_date
# ESRI shapefile attributes are stored in dbf files, which
# can not store datetimes, only dates. We still need to use
# the GDAL API with precision up to seconds (aiming at
# other output drivers of GDAL; `100` stands for GMT)
feature.SetField(native_str("StartDate"), date.year,
date.month, date.day, date.hour,
date.minute, date.second, 100)
if channel_list:
feature.SetField(native_str("Channels"),
native_str(channel_list))
if sta.latitude is not None and sta.longitude is not None:
point = ogr.Geometry(ogr.wkbPoint)
point.AddPoint(sta.longitude, sta.latitude)
feature.SetGeometry(point)
layer.CreateFeature(feature)
finally:
# Destroy the feature to free resources
feature.Destroy()
[docs]def _get_wgs84_spatial_reference():
# create the spatial reference
sr = osr.SpatialReference()
# Simpler and feels cleaner to initialize by EPSG code but that depends on
# a csv file shipping with GDAL and some GDAL environment paths being set
# correctly which was not the case out of the box in anaconda, so better
# hardcode this bit.
# sr.ImportFromEPSG(4326)
wgs84_wkt = \
"""
GEOGCS["WGS 84",
DATUM["WGS_1984",
SPHEROID["WGS 84",6378137,298.257223563,
AUTHORITY["EPSG","7030"]],
AUTHORITY["EPSG","6326"]],
PRIMEM["Greenwich",0,
AUTHORITY["EPSG","8901"]],
UNIT["degree",0.0174532925199433,
AUTHORITY["EPSG","9122"]],
AUTHORITY["EPSG","4326"]]
"""
sr.ImportFromWkt(wgs84_wkt)
return sr
[docs]def _create_layer(data_source, layer_name, field_definitions):
sr = _get_wgs84_spatial_reference()
layer = data_source.CreateLayer(native_str(layer_name), sr,
ogr.wkbPoint)
# Add the fields we're interested in
for name, type_, width, precision in field_definitions:
field = ogr.FieldDefn(native_str(name), type_)
if width is not None:
field.SetWidth(width)
if precision is not None:
field.SetPrecision(precision)
layer.CreateField(field)
return layer
if __name__ == '__main__':
import doctest
doctest.testmod(exclude_empty=True)