Skip to content

coded_location (module)

coded_location

This module contains classes and functions for coded locations: resolving map coordinates to a specific grid resolution in degrees.

Coded locations can also be gathered into coded location bins at a coarser resolution, in order to partition a large dataset into more manageable chunks:

A chart showing an example of coded location binning over a New Zealand
grid at 0.1 and 0.5 degrees

CodedLocation(lat, lon, resolution) dataclass

A location resolved to the nearest point on a grid with given resolution (degrees).

Refer to https://stackoverflow.com/a/28750072 for the techniques used here to calculate decimal places.

Create a CodedLocation instance.

Parameters:

Name Type Description Default
lat float

latitude

required
lon float

longitude

required
resolution float

the resolution used to resolve the location

required
Source code in nzshm_common/location/coded_location.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def __init__(self, lat: float, lon: float, resolution: float) -> None:
    """
    Create a CodedLocation instance.

    Arguments:
        lat: latitude
        lon: longitude
        resolution: the resolution used to resolve the location
    """
    assert 0 < resolution < 180, "Resolution must be between 0 and 180 degrees."

    self.grid_res = decimal.Decimal(str(resolution).rstrip("0"))
    self.display_places = max(abs(self.grid_res.as_tuple().exponent), 1)  # type: ignore

    div_res = 1 / float(self.grid_res)
    places = abs(decimal.Decimal(div_res).as_tuple().exponent)  # type: ignore

    self.lon = round(lon * div_res, places) / div_res
    self.lat = round(lat * div_res, places) / div_res
    self.resolution = resolution

    self._code = f"{self.lat:.{self.display_places}f}~{self.lon:.{self.display_places}f}"

as_tuple: LatLon property

Convert coded location value to a LatLon(latitude, longitude) named tuple.

Example
>>> from nzshm_common import location
>>> location.get_locations(["CHC"])[0]
CodedLocation(lat=-43.53, lon=172.63, resolution=0.001)
>>> latitude, longitude = location.get_locations(["CHC"])[0].as_tuple
>>> latitude
-43.53

code: str property

The string code for the location expressed as "latitude~longitude".

String codes are padded to a number of decimal places appropriate to the resolution property of the coded location.

Examples:

>>> from nzshm_common import location
>>> location.get_locations(["CHC"])[0].code
'-43.530~172.630'
>>> location.get_locations(["CHC"], resolution=0.1)[0].code
'-43.5~172.6'

downsample(resolution)

Create a downsampled CodedLocation with a coarser resolution.

Examples:

>>> from nzshm_common import location
>>> loc_akl = location.get_locations(["AKL"])[0]
>>> loc_akl
CodedLocation(lat=-36.87, lon=174.77, resolution=0.001)
>>> loc_akl.downsample(0.1)
CodedLocation(lat=-36.9, lon=174.8, resolution=0.1)
>>> loc_akl.downsample(0.5)
CodedLocation(lat=-37.0, lon=175.0, resolution=0.5)
Source code in nzshm_common/location/coded_location.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
def downsample(self, resolution: float) -> "CodedLocation":
    """
    Create a downsampled CodedLocation with a coarser resolution.

    Examples:
        ```py
        >>> from nzshm_common import location
        >>> loc_akl = location.get_locations(["AKL"])[0]
        >>> loc_akl
        CodedLocation(lat=-36.87, lon=174.77, resolution=0.001)
        >>> loc_akl.downsample(0.1)
        CodedLocation(lat=-36.9, lon=174.8, resolution=0.1)
        >>> loc_akl.downsample(0.5)
        CodedLocation(lat=-37.0, lon=175.0, resolution=0.5)
        ```
    """
    return CodedLocation(lat=self.lat, lon=self.lon, resolution=resolution)

from_tuple(location, resolution=DEFAULT_RESOLUTION) classmethod

Create a CodedLocation from a tuple.

Parameters:

Name Type Description Default
location LatLon

a structure containing a latitude and longitude, in that order

required
resolution float

coordinate resolution in degrees

DEFAULT_RESOLUTION

Examples:

Convert a single location:

>>> from nzshm_common import CodedLocation
>>> CodedLocation.from_tuple((-36.87, 174.77))
CodedLocation(lat=-36.87, lon=174.77, resolution=0.001)
>>> from nzshm_common import LatLon
>>> CodedLocation.from_tuple(LatLon(-36.87, 174.77))
CodedLocation(lat=-36.87, lon=174.77, resolution=0.001)

Convert a list of locations:

>>> location_list = [(-36.111, 174.111), (-36.222, 174.222)]
>>> list(map(CodedLocation.from_tuple, location_list))
[
    CodedLocation(lat=-36.111, lon=174.111, resolution=0.001),
    CodedLocation(lat=-36.222, lon=174.222, resolution=0.001)
]

Convert a list of locations with a custom resolution:

>>> from functools import partial
>>> lo_res = partial(CodedLocation.from_tuple, resolution=0.1)
>>> list(map(lo_res, location_list))
[
    CodedLocation(lat=-36.1, lon=174.1, resolution=0.1),
    CodedLocation(lat=-36.2, lon=174.2, resolution=0.1)
]
Source code in nzshm_common/location/coded_location.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
@classmethod
def from_tuple(cls, location: LatLon, resolution: float = DEFAULT_RESOLUTION) -> "CodedLocation":
    """
    Create a `CodedLocation` from a tuple.

    Parameters:
        location: a structure containing a latitude and longitude, in that order
        resolution: coordinate resolution in degrees

    Examples:
        Convert a single location:
        >>> from nzshm_common import CodedLocation
        >>> CodedLocation.from_tuple((-36.87, 174.77))
        CodedLocation(lat=-36.87, lon=174.77, resolution=0.001)

        >>> from nzshm_common import LatLon
        >>> CodedLocation.from_tuple(LatLon(-36.87, 174.77))
        CodedLocation(lat=-36.87, lon=174.77, resolution=0.001)

        Convert a list of locations:
        >>> location_list = [(-36.111, 174.111), (-36.222, 174.222)]
        >>> list(map(CodedLocation.from_tuple, location_list))
        [
            CodedLocation(lat=-36.111, lon=174.111, resolution=0.001),
            CodedLocation(lat=-36.222, lon=174.222, resolution=0.001)
        ]

        Convert a list of locations with a custom resolution:
        >>> from functools import partial
        >>> lo_res = partial(CodedLocation.from_tuple, resolution=0.1)
        >>> list(map(lo_res, location_list))
        [
            CodedLocation(lat=-36.1, lon=174.1, resolution=0.1),
            CodedLocation(lat=-36.2, lon=174.2, resolution=0.1)
        ]
    """
    return CodedLocation(lat=location[0], lon=location[1], resolution=resolution)

resample(resolution)

Create a resampled CodedLocation with a finer resolution.

This operation will not recover precision lost in previous downsampling.

Examples:

>>> loc
CodedLocation(lat=-36.1, lon=174.1, resolution=0.1)
>>> loc.resample(0.01)
CodedLocation(lat=-36.1, lon=174.1, resolution=0.01)
>>> loc.resample(0.01).code
'-36.10~174.10'
Source code in nzshm_common/location/coded_location.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
def resample(self, resolution: float) -> "CodedLocation":
    """
    Create a resampled CodedLocation with a finer resolution.

    This operation will not recover precision lost in previous downsampling.

    Examples:
        ```py
        >>> loc
        CodedLocation(lat=-36.1, lon=174.1, resolution=0.1)
        >>> loc.resample(0.01)
        CodedLocation(lat=-36.1, lon=174.1, resolution=0.01)
        >>> loc.resample(0.01).code
        '-36.10~174.10'
        ```
    """
    return self.downsample(resolution)

CodedLocationBin(reference_point, bin_resolution, locations=None)

A collection of CodedLocation values, gathered into bins at a coarser resolution.

Create a CodedLocationBin instance.

Parameters:

Name Type Description Default
reference_point CodedLocation

the downsampled coordinate use as a reference point for the collection.

required
bin_resolution float

the coarser-level resolution of the bin

required
locations Optional[Iterable[CodedLocation]]

a collection of CodedLocation values

None
Source code in nzshm_common/location/coded_location.py
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
def __init__(
    self, reference_point: CodedLocation, bin_resolution: float, locations: Optional[Iterable[CodedLocation]] = None
):
    """
    Create a CodedLocationBin instance.

    Arguments:
        reference_point: the downsampled coordinate use as a reference point for the collection.
        bin_resolution: the coarser-level resolution of the bin
        locations: a collection of CodedLocation values
    """
    self.reference_point = reference_point
    self.bin_resolution = bin_resolution

    if locations is not None:
        self.locations: List[CodedLocation] = list(locations)
    else:
        self.locations: List[CodedLocation] = list()

code: str property

The string code for the reference point, expressed as "latitude~longitude".

This can be used as a unique key when accessing a dictionary of bins.

Example
>>> my_bin = list(bins.values())[0]
>>> my_bin
CodedLocationBin(12 locations near -37.0~175.0 below resolution 0.5)
>>> bins[my_bin.code] == my_bin
True

bin_locations(locations, at_resolution, sort_bins=True)

Collect CodedLocations into a dictionary of bins at a coarser resolution.

Bin selection is based on the CodedLocation.downsample method, reducing coordinate precision.

Parameters:

Name Type Description Default
locations Iterable[CodedLocation]

a collection of CodedLocations at a finer resolution

required
at_resolution float

the resolution used when creating CodedLocationBins

required
sort_bins bool

whether to sort the bins and their members

True

Returns:

Type Description
OrderedDict[str, CodedLocationBin]

an ordered dictionary of bins, keyed on the CodedLocationBin.reference_point.code property.

Examples:

>>> from nzshm_common import grids, location
>>> grid_locs = grids.get_location_grid('NZ_0_1_NB_1_1', resolution=0.1)
>>> grid_bins = bin_locations(grid_locs, at_resolution=0.5)
>>> for location_bin in grid_bins:
...     for coded_loc in bin:
...         # Do a thing

To preserve location ordering:

>>> grid_bins = bin_locations(grid_locs, 0.25, sort_bins=False)
Source code in nzshm_common/location/coded_location.py
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
def bin_locations(
    locations: Iterable[CodedLocation], at_resolution: float, sort_bins: bool = True
) -> OrderedDict[str, CodedLocationBin]:
    """
    Collect CodedLocations into a dictionary of bins at a coarser resolution.

    Bin selection is based on the `CodedLocation.downsample` method, reducing
    coordinate precision.

    Arguments:
        locations: a collection of CodedLocations at a finer resolution
        at_resolution: the resolution used when creating CodedLocationBins
        sort_bins: whether to sort the bins and their members

    Returns:
        an ordered dictionary of bins, keyed on the `CodedLocationBin.reference_point.code` property.

    Examples:
        >>> from nzshm_common import grids, location
        >>> grid_locs = grids.get_location_grid('NZ_0_1_NB_1_1', resolution=0.1)
        >>> grid_bins = bin_locations(grid_locs, at_resolution=0.5)
        >>> for location_bin in grid_bins:
        ...     for coded_loc in bin:
        ...         # Do a thing

        To preserve location ordering:
        >>> grid_bins = bin_locations(grid_locs, 0.25, sort_bins=False)
    """
    bin_dict: OrderedDict[str, CodedLocationBin] = OrderedDict()

    max_resolution = 0.0

    for location in locations:
        max_resolution = max(max_resolution, location.resolution)
        coded_loc = location.downsample(at_resolution)
        bin_code = coded_loc._code
        if bin_code not in bin_dict:
            bin_dict[bin_code] = CodedLocationBin(coded_loc, at_resolution, [])

        bin_dict[bin_code].locations.append(location)

    if max_resolution > at_resolution:
        warn_msg = (
            f"Found locations up to {max_resolution} degree resolution. "
            + f"Binning expected to downsample to a {at_resolution} degree resolution."
        )
        warnings.warn(warn_msg, stacklevel=2)

    if sort_bins:
        # Sort the bins themselves.
        bin_dict = OrderedDict(sorted(bin_dict.items(), key=lambda item: item[1].reference_point))

        # Sort CodedLocations within each bin.
        for location_bin in bin_dict.values():
            location_bin.locations.sort()

    return bin_dict