From bfde5ee28c83c1f2adf41dc9f5943d5ef90b7b2e Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Mon, 29 Aug 2022 15:39:01 -0400 Subject: [PATCH 01/13] addressing behavior in transforms with dimensionless positions --- CHANGELOG.md | 3 +++ lunarsky/mcmf.py | 3 +++ lunarsky/moon.py | 4 ++-- lunarsky/spice_utils.py | 12 ++++++++---- lunarsky/tests/test_spice.py | 3 ++- lunarsky/tests/test_transforms.py | 20 ++++++++++++++++++++ lunarsky/time.py | 2 +- lunarsky/topo.py | 4 +++- 8 files changed, 42 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2fdd70..e23310f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## Unreleased ## Fixed +- Accept tuple for location in Time class (in this case, assumes EarthLocation) +- Use newer PCK file in unit test with Earth positioning. +- Match behavior of astropy when transforming non-unit cartesian positions without units (treat as unitspherical using direction info only) - Now tracking available lunar station_ids, instead of incrementing a counter naively. ## [0.1.2] -- 2022-01-17 diff --git a/lunarsky/mcmf.py b/lunarsky/mcmf.py index 6bdfffb..88b85b9 100644 --- a/lunarsky/mcmf.py +++ b/lunarsky/mcmf.py @@ -94,6 +94,9 @@ def make_transform(coo, toframe): newrepr = coo_cart.transform(mats).reshape(shape_out) + if is_unitspherical: + newrepr = newrepr.represent_as(UnitSphericalRepresentation) + return toframe.realize_frame(newrepr) diff --git a/lunarsky/moon.py b/lunarsky/moon.py index 3f6de94..68441e2 100644 --- a/lunarsky/moon.py +++ b/lunarsky/moon.py @@ -10,7 +10,7 @@ ) from astropy.coordinates.attributes import Attribute -from .spice_utils import remove_topo +from .spice_utils import remove_topo, LUNAR_RADIUS __all__ = ["MoonLocation", "MoonLocationAttribute"] @@ -113,7 +113,7 @@ class MoonLocation(u.Quantity): _location_dtype = np.dtype({"names": ["x", "y", "z"], "formats": [np.float64] * 3}) _array_dtype = np.dtype((np.float64, (3,))) - _lunar_radius = 1737.1e3 # m + _lunar_radius = LUNAR_RADIUS # Manage the set of defined ephemerides. # Class attributes only diff --git a/lunarsky/spice_utils.py b/lunarsky/spice_utils.py index cbec047..5563028 100644 --- a/lunarsky/spice_utils.py +++ b/lunarsky/spice_utils.py @@ -12,9 +12,13 @@ from .data import DATA_PATH _J2000 = Time("J2000") +LUNAR_RADIUS = 1737.1e3 # m TEMPORARY_KERNEL_DIR = tempfile.TemporaryDirectory() +# TODO --> Look for updated kernels and coordinate definitions. +# Use https://naif.jpl.nasa.gov/pub/naif/MER/misc/pck/pck00008.tpc for lunar radii + def check_is_loaded(search): """ @@ -53,7 +57,6 @@ def furnish_kernels(): "fk/satellites/moon_080317.tf", "fk/satellites/moon_assoc_me.tf", ] - kernel_paths = [os.path.join(DATA_PATH, kn) for kn in kernel_names] for kp in kernel_paths: spice.furnsh(kp) @@ -94,9 +97,10 @@ def lunar_surface_ephem(latitude, longitude, station_num=98): lat = np.radians(latitude) lon = np.radians(longitude) - lunar_radius = 1737.1 # km ets = np.array([spice.str2et("1950-01-01"), spice.str2et("2150-01-01")]) - pos_mcmf = spice.latrec(lunar_radius, lon, lat) # TODO Use MoonLocation instead? + pos_mcmf = spice.latrec( + LUNAR_RADIUS / 1e3, lon, lat + ) # TODO Use MoonLocation instead? states = np.zeros((len(ets), 6)) states[:, :3] = np.repeat(pos_mcmf[None, :], len(ets), axis=0) @@ -207,7 +211,7 @@ def remove_topo(station_num): frame_vars = [s.format(idnum, station_name, fm_center_id) for s in fmt_vars] - # Handle a glitch in spiceypy for older versions of numpy + # Handle a bug in spiceypy for older versions of numpy if np.str_ is None: return for var in frame_vars: diff --git a/lunarsky/tests/test_spice.py b/lunarsky/tests/test_spice.py index 9cdf724..9efc6dc 100644 --- a/lunarsky/tests/test_spice.py +++ b/lunarsky/tests/test_spice.py @@ -64,7 +64,8 @@ def test_spice_earth(grcat): ) # One more kernel is needed for the ITRF93 frame. - kname = "pck/earth_latest_high_prec.bpc" + # kname = "pck/earth_latest_high_prec.bpc" + kname = "pck/earth_000101_221031_220807.bpc" _naif_kernel_url = "https://naif.jpl.nasa.gov/pub/naif/generic_kernels" kurl = [_naif_kernel_url + "/" + kname] kernpath = download_files_in_parallel( diff --git a/lunarsky/tests/test_transforms.py b/lunarsky/tests/test_transforms.py index 930592c..af3282b 100644 --- a/lunarsky/tests/test_transforms.py +++ b/lunarsky/tests/test_transforms.py @@ -264,3 +264,23 @@ def test_incompatible_transform(fromframe): src = lunarsky.SkyCoord(coo) with pytest.raises(IncompatibleShapeError): src.transform_to(ltop) + + +def test_finite_vs_spherical(): + # Transform MCMF coordinates with distance and without + # Assumes infinite distance if no unit given, as astropy does. + R0 = 2e3 + N = 100 + phi = np.linspace(0, 2 * np.pi, N) + xyz = R0 * un.km * np.array([np.cos(phi), np.sin(phi), np.zeros(N)]) + with_units = lunarsky.SkyCoord(lunarsky.MCMF(*xyz)) + xyz = R0 * np.array([np.cos(phi), np.sin(phi), np.zeros(N)]) + sans_units = lunarsky.SkyCoord(lunarsky.MCMF(*xyz)) + + loc = lunarsky.MoonLocation.from_selenodetic(lon=180 * un.deg, lat=0, height=0) + altaz_with_units = with_units.transform_to(lunarsky.LunarTopo(location=loc)) + altaz_sans_units = sans_units.transform_to(lunarsky.LunarTopo(location=loc)) + + radius = lunarsky.spice_utils.LUNAR_RADIUS * un.m + assert np.all(altaz_with_units.distance < radius + R0 * un.km) + assert_quantity_allclose(altaz_sans_units.distance, 1.0) diff --git a/lunarsky/time.py b/lunarsky/time.py index 00837a8..cf2d8f6 100644 --- a/lunarsky/time.py +++ b/lunarsky/time.py @@ -33,7 +33,7 @@ def __init__( ): super_loc = None - if isinstance(location, EarthLocation): + if isinstance(location, (EarthLocation, tuple)): super_loc = location super().__init__( diff --git a/lunarsky/topo.py b/lunarsky/topo.py index 4bc1cef..1e9affa 100644 --- a/lunarsky/topo.py +++ b/lunarsky/topo.py @@ -180,11 +180,13 @@ def make_transform(coo, toframe): ) * un.km ) - coo_cart -= CartesianRepresentation((orig_posvel.T)[:3]) newrepr = coo_cart.transform(mats).reshape(shape_out) + if is_unitspherical: + newrepr = newrepr.represent_as(UnitSphericalRepresentation) + return toframe.realize_frame(newrepr) From c5a125ac097d8be8c37e84cbc0b31afa1a41ec96 Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Sat, 17 Sep 2022 17:50:50 -0400 Subject: [PATCH 02/13] pre-commit warning --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 06e6d7e..74835b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -exclude: '(^lunarsky/data/*)' +exclude: '(^lunarsky/data/)' repos: - repo: git://github.com/pre-commit/pre-commit-hooks From aea2aeaa2b1e59c74d733f4b59ae56a24645ce98 Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Sat, 17 Sep 2022 18:01:18 -0400 Subject: [PATCH 03/13] try using pre-commit github action --- .github/workflows/testsuite.yaml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/testsuite.yaml b/.github/workflows/testsuite.yaml index 9810ba0..0822a2c 100644 --- a/.github/workflows/testsuite.yaml +++ b/.github/workflows/testsuite.yaml @@ -18,13 +18,9 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.8 - - name: Setup Environment - run: | - pip install pre-commit - - name: Linting - run: | - pre-commit install - pre-commit run -a + - uses: pre-commit/action@v3.0.0 + with: + extra_args: -a datacache: name: Cache Data runs-on: ubuntu-latest From 76692bd50a68abacf33df9f2f05bbac6f88a5229 Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Sat, 17 Sep 2022 18:35:21 -0400 Subject: [PATCH 04/13] Revert "try using pre-commit github action" This reverts commit aea2aeaa2b1e59c74d733f4b59ae56a24645ce98. --- .github/workflows/testsuite.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/testsuite.yaml b/.github/workflows/testsuite.yaml index 0822a2c..9810ba0 100644 --- a/.github/workflows/testsuite.yaml +++ b/.github/workflows/testsuite.yaml @@ -18,9 +18,13 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.8 - - uses: pre-commit/action@v3.0.0 - with: - extra_args: -a + - name: Setup Environment + run: | + pip install pre-commit + - name: Linting + run: | + pre-commit install + pre-commit run -a datacache: name: Cache Data runs-on: ubuntu-latest From f375d5634c9c8bfaea126b380e45ff31e077e0de Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Sat, 1 Oct 2022 15:56:10 -0400 Subject: [PATCH 05/13] change version of pre-commit in config --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 74835b6..6e04254 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,8 +1,8 @@ exclude: '(^lunarsky/data/)' repos: - - repo: git://github.com/pre-commit/pre-commit-hooks - rev: v2.5.0 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 hooks: - id: trailing-whitespace - id: check-ast From bf869eaa46cfb3a5c3c80de8282862331e552cef Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Sat, 1 Oct 2022 16:07:42 -0400 Subject: [PATCH 06/13] fix black version --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e04254..6c7fb1c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: - flake8-comprehensions - flake8-pytest - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 22.3.0 hooks: - id: black language_version: python3.8 From 4d22c8346732449bb4052b19d8733f630f9b35d0 Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Sat, 1 Oct 2022 16:17:53 -0400 Subject: [PATCH 07/13] fix kernel URL in test --- lunarsky/tests/test_spice.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lunarsky/tests/test_spice.py b/lunarsky/tests/test_spice.py index 9efc6dc..9cdf724 100644 --- a/lunarsky/tests/test_spice.py +++ b/lunarsky/tests/test_spice.py @@ -64,8 +64,7 @@ def test_spice_earth(grcat): ) # One more kernel is needed for the ITRF93 frame. - # kname = "pck/earth_latest_high_prec.bpc" - kname = "pck/earth_000101_221031_220807.bpc" + kname = "pck/earth_latest_high_prec.bpc" _naif_kernel_url = "https://naif.jpl.nasa.gov/pub/naif/generic_kernels" kurl = [_naif_kernel_url + "/" + kname] kernpath = download_files_in_parallel( From 0ab51d98d73d3ffdb3c7b16217ca86dae25a827b Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Sat, 1 Oct 2022 16:24:35 -0400 Subject: [PATCH 08/13] update changelog and drop py3.6 from tests --- .github/workflows/testsuite.yaml | 2 +- CHANGELOG.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testsuite.yaml b/.github/workflows/testsuite.yaml index 9810ba0..032998a 100644 --- a/.github/workflows/testsuite.yaml +++ b/.github/workflows/testsuite.yaml @@ -62,7 +62,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] - python-version: [3.6, 3.7, 3.8] + python-version: [3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index e23310f..38e3f95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,15 @@ ## Unreleased ## Fixed +- Updated version of pre-commit-hooks used - Accept tuple for location in Time class (in this case, assumes EarthLocation) - Use newer PCK file in unit test with Earth positioning. - Match behavior of astropy when transforming non-unit cartesian positions without units (treat as unitspherical using direction info only) - Now tracking available lunar station_ids, instead of incrementing a counter naively. +## Deprecated +- Dropping support for Python 3.6 + ## [0.1.2] -- 2022-01-17 ## Added From 569a8abc302e7d4c2fce91660564901111ace850 Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Sun, 2 Oct 2022 16:22:28 -0400 Subject: [PATCH 09/13] warn on non-unity dimensionless coordinates --- lunarsky/tests/test_transforms.py | 5 +++-- lunarsky/topo.py | 9 ++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lunarsky/tests/test_transforms.py b/lunarsky/tests/test_transforms.py index af3282b..1da52c4 100644 --- a/lunarsky/tests/test_transforms.py +++ b/lunarsky/tests/test_transforms.py @@ -3,7 +3,7 @@ from lunarsky.time import Time, TimeDelta import astropy.coordinates as ac from astropy import units as un -from astropy.utils import IncompatibleShapeError +from astropy.utils import IncompatibleShapeError, exceptions from astropy.tests.helper import assert_quantity_allclose import lunarsky import pytest @@ -279,7 +279,8 @@ def test_finite_vs_spherical(): loc = lunarsky.MoonLocation.from_selenodetic(lon=180 * un.deg, lat=0, height=0) altaz_with_units = with_units.transform_to(lunarsky.LunarTopo(location=loc)) - altaz_sans_units = sans_units.transform_to(lunarsky.LunarTopo(location=loc)) + with pytest.warns(exceptions.AstropyUserWarning, match="Coordinate vectors do not "): + altaz_sans_units = sans_units.transform_to(lunarsky.LunarTopo(location=loc)) radius = lunarsky.spice_utils.LUNAR_RADIUS * un.m assert np.all(altaz_with_units.distance < radius + R0 * un.km) diff --git a/lunarsky/topo.py b/lunarsky/topo.py index 1e9affa..d5ae904 100644 --- a/lunarsky/topo.py +++ b/lunarsky/topo.py @@ -1,4 +1,5 @@ import numpy as np +import warnings from astropy.utils.decorators import format_doc from astropy.coordinates.representation import ( SphericalRepresentation, @@ -6,7 +7,7 @@ UnitSphericalRepresentation, CartesianRepresentation, ) -from astropy.utils import check_broadcast +from astropy.utils import check_broadcast, exceptions from astropy.coordinates.baseframe import ( BaseCoordinateFrame, base_doc, @@ -185,6 +186,12 @@ def make_transform(coo, toframe): newrepr = coo_cart.transform(mats).reshape(shape_out) if is_unitspherical: + if np.any(newrepr.norm() > 1.0): + warnings.warn( + "Coordinates do not all have unit magnitude, but will be treated as unit spherical," + "Define coordinates as Quantity or normalize to remove this warning.", + exceptions.AstropyUserWarning, + ) newrepr = newrepr.represent_as(UnitSphericalRepresentation) return toframe.realize_frame(newrepr) From 63707c29214f6895b547ef175df33eebb8f25fd5 Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Sun, 2 Oct 2022 16:26:22 -0400 Subject: [PATCH 10/13] fix test --- lunarsky/tests/test_transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lunarsky/tests/test_transforms.py b/lunarsky/tests/test_transforms.py index 1da52c4..bcd9140 100644 --- a/lunarsky/tests/test_transforms.py +++ b/lunarsky/tests/test_transforms.py @@ -279,7 +279,7 @@ def test_finite_vs_spherical(): loc = lunarsky.MoonLocation.from_selenodetic(lon=180 * un.deg, lat=0, height=0) altaz_with_units = with_units.transform_to(lunarsky.LunarTopo(location=loc)) - with pytest.warns(exceptions.AstropyUserWarning, match="Coordinate vectors do not "): + with pytest.warns(exceptions.AstropyUserWarning, match="Coordinates do not "): altaz_sans_units = sans_units.transform_to(lunarsky.LunarTopo(location=loc)) radius = lunarsky.spice_utils.LUNAR_RADIUS * un.m From 37ed9711e87ddb158bbaf8d73ffd5892ce25cd95 Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Sun, 2 Oct 2022 16:52:07 -0400 Subject: [PATCH 11/13] suppress dubious year warnings for some tests --- lunarsky/moon.py | 2 +- lunarsky/tests/test_transforms.py | 6 ++++-- lunarsky/topo.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lunarsky/moon.py b/lunarsky/moon.py index 68441e2..d5ee239 100644 --- a/lunarsky/moon.py +++ b/lunarsky/moon.py @@ -282,7 +282,7 @@ def from_selenodetic(cls, lon, lat, height=0.0): the mean position of the "sub-Earth" point on the lunar surface. """ - lon = Longitude(lon, u.degree, wrap_angle=180 * u.degree, copy=False) + lon = Longitude(lon, u.degree, copy=False).wrap_at(180 * u.degree) lat = Latitude(lat, u.degree, copy=False) # don't convert to m by default, so we can use the height unit below. if not isinstance(height, u.Quantity): diff --git a/lunarsky/tests/test_transforms.py b/lunarsky/tests/test_transforms.py index bcd9140..1ead075 100644 --- a/lunarsky/tests/test_transforms.py +++ b/lunarsky/tests/test_transforms.py @@ -25,6 +25,7 @@ @pytest.mark.parametrize("time", jd_10yr) @pytest.mark.parametrize("lat,lon", latlons_grid) +@pytest.mark.filterwarnings("ignore::erfa.ErfaWarning") def test_icrs_to_topo_long_time(time, lat, lon, grcat): # Check that the following transformation paths are equivalent: # ICRS -> MCMF -> TOPO @@ -48,9 +49,9 @@ def test_icrs_to_topo_long_time(time, lat, lon, grcat): "time, lat, lon", [ (t0, 11.2, 1.4), - (t0, (10.3, 11.2), (0.0, 1.4)), + (t0, [10.3, 11.2], [0.0, 1.4]), (jd_4mo[:2], 10.3, 0.0), - (jd_4mo[:2], (10.3, 11.2), (0.0, 1.4)), + (jd_4mo[:2], [10.3, 11.2], [0.0, 1.4]), ], ) def test_transform_loops(obj, path, time, lat, lon): @@ -84,6 +85,7 @@ def test_topo_to_topo(): assert new.az.deg == 90 +@pytest.mark.filterwarnings("ignore::erfa.ErfaWarning") def test_mcmf_to_mcmf(): # Transform MCMF positions to MCMF frame half a lunar sidereal day later. # Assert that the new positions are roughly close to 180 deg from the original. diff --git a/lunarsky/topo.py b/lunarsky/topo.py index d5ae904..52bc3bf 100644 --- a/lunarsky/topo.py +++ b/lunarsky/topo.py @@ -186,7 +186,7 @@ def make_transform(coo, toframe): newrepr = coo_cart.transform(mats).reshape(shape_out) if is_unitspherical: - if np.any(newrepr.norm() > 1.0): + if not np.allclose(newrepr.norm(), 1.0): warnings.warn( "Coordinates do not all have unit magnitude, but will be treated as unit spherical," "Define coordinates as Quantity or normalize to remove this warning.", From 36694c4b84b9249141b2da8dd764ba5d36ef6b70 Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Mon, 3 Oct 2022 14:53:42 -0400 Subject: [PATCH 12/13] cover tuple case in time --- lunarsky/tests/test_time.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lunarsky/tests/test_time.py b/lunarsky/tests/test_time.py index a0dfa2c..c703157 100644 --- a/lunarsky/tests/test_time.py +++ b/lunarsky/tests/test_time.py @@ -1,5 +1,5 @@ import numpy as np -from astropy.coordinates import ICRS +from astropy.coordinates import ICRS, EarthLocation from astropy.time import TimeDelta import pytest @@ -23,3 +23,15 @@ def test_sidereal_time_calculation(lat, lon): src = SkyCoord(alt="90d", az="0d", frame="lunartopo", obstime=tt, location=loc) lst = tt.sidereal_time("mean") assert np.isclose(lst.deg, src.transform_to(ICRS()).ra.deg, atol=1e-4) + + +def test_earthloc(): + lat = -30.0 + lon = 127.0 + + t0 = Time( + "2020-01-01T00:00:00", location=EarthLocation.from_geodetic(lon=lon, lat=lat) + ) + t1 = Time("2020-01-01T00:00:00", location=(lon, lat)) + + assert t0.location == t1.location From 1461cda92732b82096ff37838d81f89406dd7312 Mon Sep 17 00:00:00 2001 From: Adam Lanman Date: Tue, 11 Oct 2022 12:17:16 -0400 Subject: [PATCH 13/13] minor bug with handling EarthLocation in time --- lunarsky/time.py | 2 +- lunarsky/topo.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lunarsky/time.py b/lunarsky/time.py index cf2d8f6..c786880 100644 --- a/lunarsky/time.py +++ b/lunarsky/time.py @@ -53,7 +53,7 @@ def __init__( def sidereal_time(self, kind, longitude=None, model=None): # Currently returns the zenith RA as the LST. - if self.location is None or self.location is EarthLocation: + if self.location is None or isinstance(self.location, EarthLocation): return super().sidereal_time(kind, longitude=longitude, model=model) if model is not None: diff --git a/lunarsky/topo.py b/lunarsky/topo.py index 52bc3bf..d45def0 100644 --- a/lunarsky/topo.py +++ b/lunarsky/topo.py @@ -125,6 +125,9 @@ def make_transform(coo, toframe): obstime = toframe.obstime location = toframe.location + if location is None: + raise ValueError("location must be defined for LunarTopo transformations") + # Initialize station_ids if not defined. if location.station_ids == []: location.__class__._set_site_id(location)