mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-02-04 01:04:33 +00:00
CQ/ITU zone lookups
This commit is contained in:
@@ -2,12 +2,136 @@ import logging
|
|||||||
import re
|
import re
|
||||||
from math import floor
|
from math import floor
|
||||||
|
|
||||||
|
import geopandas
|
||||||
from pyproj import Transformer
|
from pyproj import Transformer
|
||||||
|
from shapely.geometry import Point, Polygon
|
||||||
|
|
||||||
TRANSFORMER_OS_GRID_TO_WGS84 = Transformer.from_crs("EPSG:27700", "EPSG:4326")
|
TRANSFORMER_OS_GRID_TO_WGS84 = Transformer.from_crs("EPSG:27700", "EPSG:4326")
|
||||||
TRANSFORMER_IRISH_GRID_TO_WGS84 = Transformer.from_crs("EPSG:29903", "EPSG:4326")
|
TRANSFORMER_IRISH_GRID_TO_WGS84 = Transformer.from_crs("EPSG:29903", "EPSG:4326")
|
||||||
TRANSFORMER_CI_UTM_GRID_TO_WGS84 = Transformer.from_crs("+proj=utm +zone=30 +ellps=WGS84", "EPSG:4326")
|
TRANSFORMER_CI_UTM_GRID_TO_WGS84 = Transformer.from_crs("+proj=utm +zone=30 +ellps=WGS84", "EPSG:4326")
|
||||||
|
|
||||||
|
cq_zone_data = geopandas.GeoDataFrame.from_features(geopandas.read_file("datafiles/cqzones.geojson"))
|
||||||
|
itu_zone_data = geopandas.GeoDataFrame.from_features(geopandas.read_file("datafiles/ituzones.geojson"))
|
||||||
|
|
||||||
|
|
||||||
|
# Finds out which CQ zone a lat/lon point is in.
|
||||||
|
def lat_lon_to_cq_zone(lat, lon):
|
||||||
|
for index, row in cq_zone_data.iterrows():
|
||||||
|
polygon = Polygon(row["geometry"])
|
||||||
|
test_point = Point(lon, lat)
|
||||||
|
if polygon.contains(test_point):
|
||||||
|
return int(row["name"])
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Finds out which ITU zone a lat/lon point is in.
|
||||||
|
def lat_lon_to_itu_zone(lat, lon):
|
||||||
|
for index, row in itu_zone_data.iterrows():
|
||||||
|
polygon = Polygon(row["geometry"])
|
||||||
|
test_point = Point(lon, lat)
|
||||||
|
if polygon.contains(test_point):
|
||||||
|
return int(row["name"])
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Convert a Maidenhead grid reference of arbitrary precision to the lat/long of the centre point of the square.
|
||||||
|
# Returns None if the grid format is invalid.
|
||||||
|
def lat_lon_for_grid_centre(grid):
|
||||||
|
lat, lon, lat_cell_size, lon_cell_size = lat_lon_for_grid_sw_corner_plus_size(grid)
|
||||||
|
if lat is not None and lon is not None and lat_cell_size is not None and lon_cell_size is not None:
|
||||||
|
return [lat + lat_cell_size / 2.0, lon + lon_cell_size / 2.0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Convert a Maidenhead grid reference of arbitrary precision to the lat/long of the southwest corner of the square.
|
||||||
|
# Returns None if the grid format is invalid.
|
||||||
|
def lat_lon_for_grid_sw_corner(grid):
|
||||||
|
lat, lon, lat_cell_size, lon_cell_size = lat_lon_for_grid_sw_corner_plus_size(grid)
|
||||||
|
if lat is not None and lon is not None:
|
||||||
|
return [lat, lon]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Convert a Maidenhead grid reference of arbitrary precision to the lat/long of the northeast corner of the square.
|
||||||
|
# Returns None if the grid format is invalid.
|
||||||
|
def lat_lon_for_grid_ne_corner(grid):
|
||||||
|
lat, lon, lat_cell_size, lon_cell_size = lat_lon_for_grid_sw_corner_plus_size(grid)
|
||||||
|
if lat is not None and lon is not None and lat_cell_size is not None and lon_cell_size is not None:
|
||||||
|
return [lat + lat_cell_size, lon + lon_cell_size]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Convert a Maidenhead grid reference of arbitrary precision to lat/long, including in the result the size of the
|
||||||
|
# lowest grid square. This is a utility method used by the main methods that return the centre, southwest, and
|
||||||
|
# northeast coordinates of a grid square.
|
||||||
|
# The return type is always a tuple of size 4. The elements in it are None if the grid format is invalid.
|
||||||
|
def lat_lon_for_grid_sw_corner_plus_size(grid):
|
||||||
|
# Make sure we are in upper case so our maths works. Case is arbitrary for Maidenhead references
|
||||||
|
grid = grid.upper()
|
||||||
|
|
||||||
|
# Return None if our Maidenhead string is invalid or too short
|
||||||
|
length = len(grid)
|
||||||
|
if length <= 0 or (length % 2) != 0:
|
||||||
|
return (None, None, None, None)
|
||||||
|
|
||||||
|
lat = 0.0 # aggregated latitude
|
||||||
|
lon = 0.0 # aggregated longitude
|
||||||
|
lat_cell_size = 10.0 # Size in degrees latitude of the current cell. Starts at 10 and gets smaller as the calculation progresses
|
||||||
|
lon_cell_size = 20.0 # Size in degrees longitude of the current cell. Starts at 20 and gets smaller as the calculation progresses
|
||||||
|
|
||||||
|
# Iterate through blocks (two-character sections)
|
||||||
|
block = 0
|
||||||
|
while block * 2 < length:
|
||||||
|
if block % 2 == 0:
|
||||||
|
# Letters in this block
|
||||||
|
lon_cell_no = ord(grid[block * 2]) - ord('A')
|
||||||
|
lat_cell_no = ord(grid[block * 2 + 1]) - ord('A')
|
||||||
|
# Bail if the values aren't in range. Allowed values are A-R (0-17) for the first letter block, or
|
||||||
|
# A-X (0-23) thereafter.
|
||||||
|
max_cell_no = 17 if block == 0 else 23
|
||||||
|
if lat_cell_no < 0 or lat_cell_no > max_cell_no or lon_cell_no < 0 or lon_cell_no > max_cell_no:
|
||||||
|
return (None, None, None, None)
|
||||||
|
else:
|
||||||
|
# Numbers in this block
|
||||||
|
try:
|
||||||
|
lon_cell_no = int(grid[block * 2])
|
||||||
|
lat_cell_no = int(grid[block * 2 + 1])
|
||||||
|
except ValueError:
|
||||||
|
return (None, None, None, None)
|
||||||
|
# Bail if the values aren't in range 0-9
|
||||||
|
if lat_cell_no < 0 or lat_cell_no > 9 or lon_cell_no < 0 or lon_cell_no > 9:
|
||||||
|
return (None, None, None, None)
|
||||||
|
|
||||||
|
# Aggregate the angles
|
||||||
|
lat += lat_cell_no * lat_cell_size
|
||||||
|
lon += lon_cell_no * lon_cell_size
|
||||||
|
|
||||||
|
# Reduce the cell size for the next block, unless we are on the last cell.
|
||||||
|
if block * 2 < length - 2:
|
||||||
|
# Still have more work to do, so reduce the cell size
|
||||||
|
if block % 2 == 0:
|
||||||
|
# Just dealt with letters, next block will be numbers so cells will be 1/10 the current size
|
||||||
|
lat_cell_size = lat_cell_size / 10.0
|
||||||
|
lon_cell_size = lon_cell_size / 10.0
|
||||||
|
else:
|
||||||
|
# Just dealt with numbers, next block will be letters so cells will be 1/24 the current size
|
||||||
|
lat_cell_size = lat_cell_size / 24.0
|
||||||
|
lon_cell_size = lon_cell_size / 24.0
|
||||||
|
|
||||||
|
block += 1
|
||||||
|
|
||||||
|
# Offset back to (-180, -90) where the grid starts
|
||||||
|
lon -= 180.0
|
||||||
|
lat -= 90.0
|
||||||
|
|
||||||
|
# Return None values on maths errors
|
||||||
|
if any(x != x for x in [lat, lon, lat_cell_size, lon_cell_size]): # NaN check
|
||||||
|
return None, None, None, None
|
||||||
|
|
||||||
|
return lat, lon, lat_cell_size, lon_cell_size
|
||||||
|
|
||||||
|
|
||||||
# Convert a Worked All Britain or Worked All Ireland reference to a lat/lon point.
|
# Convert a Worked All Britain or Worked All Ireland reference to a lat/lon point.
|
||||||
def wab_wai_square_to_lat_lon(ref):
|
def wab_wai_square_to_lat_lon(ref):
|
||||||
@@ -20,7 +144,7 @@ def wab_wai_square_to_lat_lon(ref):
|
|||||||
elif re.match(r"^W[AV][0-9]{2}$", ref):
|
elif re.match(r"^W[AV][0-9]{2}$", ref):
|
||||||
return utm_grid_square_to_lat_lon(ref)
|
return utm_grid_square_to_lat_lon(ref)
|
||||||
else:
|
else:
|
||||||
logging.warn("Invalid WAB/WAI square: " + ref)
|
logging.warning("Invalid WAB/WAI square: " + ref)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ class Alert:
|
|||||||
if self.received_time and not self.received_time_iso:
|
if self.received_time and not self.received_time_iso:
|
||||||
self.received_time_iso = datetime.fromtimestamp(self.received_time, pytz.UTC).isoformat()
|
self.received_time_iso = datetime.fromtimestamp(self.received_time, pytz.UTC).isoformat()
|
||||||
|
|
||||||
# DX country, continent, zones etc. from callsign
|
# DX country, continent, zones etc. from callsign. CQ/ITU zone are better looked up with a location but we don't
|
||||||
|
# have a real location for alerts.
|
||||||
if self.dx_calls and self.dx_calls[0] and not self.dx_country:
|
if self.dx_calls and self.dx_calls[0] and not self.dx_country:
|
||||||
self.dx_country = lookup_helper.infer_country_from_callsign(self.dx_calls[0])
|
self.dx_country = lookup_helper.infer_country_from_callsign(self.dx_calls[0])
|
||||||
if self.dx_calls and self.dx_calls[0] and not self.dx_continent:
|
if self.dx_calls and self.dx_calls[0] and not self.dx_continent:
|
||||||
|
|||||||
19
data/spot.py
19
data/spot.py
@@ -11,6 +11,7 @@ from pyhamtools.locator import locator_to_latlong, latlong_to_locator
|
|||||||
|
|
||||||
from core.config import MAX_SPOT_AGE
|
from core.config import MAX_SPOT_AGE
|
||||||
from core.constants import MODE_ALIASES
|
from core.constants import MODE_ALIASES
|
||||||
|
from core.geo_utils import lat_lon_to_cq_zone, lat_lon_to_itu_zone
|
||||||
from core.lookup_helper import lookup_helper
|
from core.lookup_helper import lookup_helper
|
||||||
from core.sig_utils import populate_sig_ref_info, ANY_SIG_REGEX, get_ref_regex_for_sig
|
from core.sig_utils import populate_sig_ref_info, ANY_SIG_REGEX, get_ref_regex_for_sig
|
||||||
from data.sig_ref import SIGRef
|
from data.sig_ref import SIGRef
|
||||||
@@ -152,15 +153,11 @@ class Spot:
|
|||||||
if len(split) > 1 and split[1] != "#":
|
if len(split) > 1 and split[1] != "#":
|
||||||
self.dx_ssid = split[1]
|
self.dx_ssid = split[1]
|
||||||
|
|
||||||
# DX country, continent, zones etc. from callsign
|
# DX country, continent etc. from callsign
|
||||||
if self.dx_call and not self.dx_country:
|
if self.dx_call and not self.dx_country:
|
||||||
self.dx_country = lookup_helper.infer_country_from_callsign(self.dx_call)
|
self.dx_country = lookup_helper.infer_country_from_callsign(self.dx_call)
|
||||||
if self.dx_call and not self.dx_continent:
|
if self.dx_call and not self.dx_continent:
|
||||||
self.dx_continent = lookup_helper.infer_continent_from_callsign(self.dx_call)
|
self.dx_continent = lookup_helper.infer_continent_from_callsign(self.dx_call)
|
||||||
if self.dx_call and not self.dx_cq_zone:
|
|
||||||
self.dx_cq_zone = lookup_helper.infer_cq_zone_from_callsign(self.dx_call)
|
|
||||||
if self.dx_call and not self.dx_itu_zone:
|
|
||||||
self.dx_itu_zone = lookup_helper.infer_itu_zone_from_callsign(self.dx_call)
|
|
||||||
if self.dx_call and not self.dx_dxcc_id:
|
if self.dx_call and not self.dx_dxcc_id:
|
||||||
self.dx_dxcc_id = lookup_helper.infer_dxcc_id_from_callsign(self.dx_call)
|
self.dx_dxcc_id = lookup_helper.infer_dxcc_id_from_callsign(self.dx_call)
|
||||||
if self.dx_dxcc_id and not self.dx_flag:
|
if self.dx_dxcc_id and not self.dx_flag:
|
||||||
@@ -332,6 +329,18 @@ class Spot:
|
|||||||
self.dx_grid = lookup_helper.infer_grid_from_callsign_dxcc(self.dx_call)
|
self.dx_grid = lookup_helper.infer_grid_from_callsign_dxcc(self.dx_call)
|
||||||
self.dx_location_source = "DXCC"
|
self.dx_location_source = "DXCC"
|
||||||
|
|
||||||
|
# CQ and ITU zone lookup, preferably from location but failing that, from callsign
|
||||||
|
if not self.dx_cq_zone:
|
||||||
|
if self.dx_latitude:
|
||||||
|
self.dx_cq_zone = lat_lon_to_cq_zone(self.dx_latitude, self.dx_longitude)
|
||||||
|
elif self.dx_call:
|
||||||
|
self.dx_cq_zone = lookup_helper.infer_cq_zone_from_callsign(self.dx_call)
|
||||||
|
if not self.dx_itu_zone:
|
||||||
|
if self.dx_latitude:
|
||||||
|
self.dx_itu_zone = lat_lon_to_itu_zone(self.dx_latitude, self.dx_longitude)
|
||||||
|
elif self.dx_call:
|
||||||
|
self.dx_itu_zone = lookup_helper.infer_itu_zone_from_callsign(self.dx_call)
|
||||||
|
|
||||||
# DX Location is "good" if it is from a spot, or from QRZ if the callsign doesn't contain a slash, so the operator
|
# DX Location is "good" if it is from a spot, or from QRZ if the callsign doesn't contain a slash, so the operator
|
||||||
# is likely at home.
|
# is likely at home.
|
||||||
self.dx_location_good = self.dx_latitude and self.dx_longitude and (
|
self.dx_location_good = self.dx_latitude and self.dx_longitude and (
|
||||||
|
|||||||
134817
datafiles/cqzones.geojson
Normal file
134817
datafiles/cqzones.geojson
Normal file
File diff suppressed because it is too large
Load Diff
73598
datafiles/ituzones.geojson
Normal file
73598
datafiles/ituzones.geojson
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,4 +14,5 @@ prometheus_client~=0.23.1
|
|||||||
beautifulsoup4~=4.14.2
|
beautifulsoup4~=4.14.2
|
||||||
websocket-client~=1.9.0
|
websocket-client~=1.9.0
|
||||||
tornado~=6.5.4
|
tornado~=6.5.4
|
||||||
tornado_eventsource~=3.0.0
|
tornado_eventsource~=3.0.0
|
||||||
|
geopandas~=1.1.2
|
||||||
@@ -5,8 +5,10 @@ from datetime import datetime
|
|||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
import tornado
|
import tornado
|
||||||
|
from pyhamtools.locator import locator_to_latlong
|
||||||
|
|
||||||
from core.constants import SIGS
|
from core.constants import SIGS
|
||||||
|
from core.geo_utils import lat_lon_for_grid_sw_corner_plus_size, lat_lon_to_cq_zone, lat_lon_to_itu_zone
|
||||||
from core.prometheus_metrics_handler import api_requests_counter
|
from core.prometheus_metrics_handler import api_requests_counter
|
||||||
from core.sig_utils import get_ref_regex_for_sig, populate_sig_ref_info
|
from core.sig_utils import get_ref_regex_for_sig, populate_sig_ref_info
|
||||||
from core.utils import serialize_everything
|
from core.utils import serialize_everything
|
||||||
@@ -119,3 +121,61 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
|
|||||||
|
|
||||||
self.set_header("Cache-Control", "no-store")
|
self.set_header("Cache-Control", "no-store")
|
||||||
self.set_header("Content-Type", "application/json")
|
self.set_header("Content-Type", "application/json")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# API request handler for /api/v1/lookup/grid
|
||||||
|
class APILookupGridHandler(tornado.web.RequestHandler):
|
||||||
|
def initialize(self, web_server_metrics):
|
||||||
|
self.web_server_metrics = web_server_metrics
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
try:
|
||||||
|
# Metrics
|
||||||
|
self.web_server_metrics["last_api_access_time"] = datetime.now(pytz.UTC)
|
||||||
|
self.web_server_metrics["api_access_counter"] += 1
|
||||||
|
self.web_server_metrics["status"] = "OK"
|
||||||
|
api_requests_counter.inc()
|
||||||
|
|
||||||
|
# request.arguments contains lists for each param key because technically the client can supply multiple,
|
||||||
|
# reduce that to just the first entry, and convert bytes to string
|
||||||
|
query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
|
||||||
|
|
||||||
|
# "grid" query param must exist.
|
||||||
|
if "grid" in query_params.keys():
|
||||||
|
grid = query_params.get("grid").upper()
|
||||||
|
lat, lon, lat_cell_size, lon_cell_size = lat_lon_for_grid_sw_corner_plus_size(grid)
|
||||||
|
if lat is not None and lon is not None and lat_cell_size is not None and lon_cell_size is not None:
|
||||||
|
center_lat = lat + lat_cell_size / 2.0
|
||||||
|
center_lon = lon + lon_cell_size / 2.0
|
||||||
|
center_cq_zone = lat_lon_to_cq_zone(center_lat, center_lon)
|
||||||
|
center_itu_zone = lat_lon_to_itu_zone(center_lat, center_lon)
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"center" : {
|
||||||
|
"latitude": center_lat,
|
||||||
|
"longitude": center_lon,
|
||||||
|
"cq_zone": center_cq_zone,
|
||||||
|
"itu_zone": center_itu_zone
|
||||||
|
},
|
||||||
|
"southwest" : {
|
||||||
|
"latitude": lat,
|
||||||
|
"longitude": lon,
|
||||||
|
},
|
||||||
|
"northeast" : {
|
||||||
|
"latitude": lat + lat_cell_size,
|
||||||
|
"longitude": lon + lon_cell_size,
|
||||||
|
}}
|
||||||
|
self.write(json.dumps(response, default=serialize_everything))
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.write(json.dumps("Error - grid must be provided", default=serialize_everything))
|
||||||
|
self.set_status(422)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(e)
|
||||||
|
self.write(json.dumps("Error - " + str(e), default=serialize_everything))
|
||||||
|
self.set_status(500)
|
||||||
|
|
||||||
|
self.set_header("Cache-Control", "no-store")
|
||||||
|
self.set_header("Content-Type", "application/json")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from tornado.web import StaticFileHandler
|
|||||||
|
|
||||||
from server.handlers.api.addspot import APISpotHandler
|
from server.handlers.api.addspot import APISpotHandler
|
||||||
from server.handlers.api.alerts import APIAlertsHandler, APIAlertsStreamHandler
|
from server.handlers.api.alerts import APIAlertsHandler, APIAlertsStreamHandler
|
||||||
from server.handlers.api.lookups import APILookupCallHandler, APILookupSIGRefHandler
|
from server.handlers.api.lookups import APILookupCallHandler, APILookupSIGRefHandler, APILookupGridHandler
|
||||||
from server.handlers.api.options import APIOptionsHandler
|
from server.handlers.api.options import APIOptionsHandler
|
||||||
from server.handlers.api.spots import APISpotsHandler, APISpotsStreamHandler
|
from server.handlers.api.spots import APISpotsHandler, APISpotsStreamHandler
|
||||||
from server.handlers.api.status import APIStatusHandler
|
from server.handlers.api.status import APIStatusHandler
|
||||||
@@ -54,6 +54,7 @@ class WebServer:
|
|||||||
(r"/api/v1/status", APIStatusHandler, {"status_data": self.status_data, "web_server_metrics": self.web_server_metrics}),
|
(r"/api/v1/status", APIStatusHandler, {"status_data": self.status_data, "web_server_metrics": self.web_server_metrics}),
|
||||||
(r"/api/v1/lookup/call", APILookupCallHandler, {"web_server_metrics": self.web_server_metrics}),
|
(r"/api/v1/lookup/call", APILookupCallHandler, {"web_server_metrics": self.web_server_metrics}),
|
||||||
(r"/api/v1/lookup/sigref", APILookupSIGRefHandler, {"web_server_metrics": self.web_server_metrics}),
|
(r"/api/v1/lookup/sigref", APILookupSIGRefHandler, {"web_server_metrics": self.web_server_metrics}),
|
||||||
|
(r"/api/v1/lookup/grid", APILookupGridHandler, {"web_server_metrics": self.web_server_metrics}),
|
||||||
(r"/api/v1/spot", APISpotHandler, {"spots": self.spots, "web_server_metrics": self.web_server_metrics}),
|
(r"/api/v1/spot", APISpotHandler, {"spots": self.spots, "web_server_metrics": self.web_server_metrics}),
|
||||||
# Routes for templated pages
|
# Routes for templated pages
|
||||||
(r"/", PageTemplateHandler, {"template_name": "spots", "web_server_metrics": self.web_server_metrics}),
|
(r"/", PageTemplateHandler, {"template_name": "spots", "web_server_metrics": self.web_server_metrics}),
|
||||||
|
|||||||
@@ -653,6 +653,80 @@ paths:
|
|||||||
example: "Failed"
|
example: "Failed"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/lookup/grid:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Utilities
|
||||||
|
summary: Look up grid details
|
||||||
|
description: Perform a lookup of data about a Maidenhead grid square.
|
||||||
|
operationId: grid
|
||||||
|
parameters:
|
||||||
|
- name: grid
|
||||||
|
in: query
|
||||||
|
description: Maidenhead grid, to any accuracy
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
example: "AA00aa"
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
center:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
latitude:
|
||||||
|
type: number
|
||||||
|
description: Latitude of the centre of the grid reference.
|
||||||
|
example: 0.0
|
||||||
|
longitude:
|
||||||
|
type: number
|
||||||
|
description: Latitude of the centre of the grid reference.
|
||||||
|
example: 0.0
|
||||||
|
cq_zone:
|
||||||
|
type: number
|
||||||
|
description: CQ zone of the centre of the grid reference.
|
||||||
|
example: 1
|
||||||
|
itu_zone:
|
||||||
|
type: number
|
||||||
|
description: ITU zone of the centre of the grid reference.
|
||||||
|
example: 1
|
||||||
|
southwest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
latitude:
|
||||||
|
type: number
|
||||||
|
description: Latitude of the south-west corner of the grid square.
|
||||||
|
example: 0.0
|
||||||
|
longitude:
|
||||||
|
type: number
|
||||||
|
description: Latitude of the south-west corner of the grid square.
|
||||||
|
example: 0.0
|
||||||
|
northeast:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
latitude:
|
||||||
|
type: number
|
||||||
|
description: Latitude of the north-east corner of the grid square.
|
||||||
|
example: 0.0
|
||||||
|
longitude:
|
||||||
|
type: number
|
||||||
|
description: Latitude of the north-east corner of the grid square.
|
||||||
|
example: 0.0
|
||||||
|
'422':
|
||||||
|
description: Validation error e.g. reference format incorrect
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "Failed"
|
||||||
|
|
||||||
|
|
||||||
/spot:
|
/spot:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
Reference in New Issue
Block a user