start of proj4 support (part of GPS support)

This commit is contained in:
Jenny Danzmayr 2024-08-13 21:17:36 +02:00
parent edba90481e
commit aa49840806
4 changed files with 129 additions and 2 deletions

View file

@ -9,6 +9,7 @@ from ninja import Router as APIRouter
from pydantic import Field as APIField from pydantic import Field as APIField
from pydantic import PositiveInt from pydantic import PositiveInt
from c3nav import settings
from c3nav.api.auth import auth_permission_responses, auth_responses, validate_responses from c3nav.api.auth import auth_permission_responses, auth_responses, validate_responses
from c3nav.api.exceptions import API404, APIPermissionDenied, APIRequestValidationFailed from c3nav.api.exceptions import API404, APIPermissionDenied, APIRequestValidationFailed
from c3nav.api.schema import BaseSchema from c3nav.api.schema import BaseSchema
@ -19,8 +20,9 @@ from c3nav.mapdata.models.locations import DynamicLocation, LocationRedirect, Po
from c3nav.mapdata.schemas.filters import BySearchableFilter, RemoveGeometryFilter from c3nav.mapdata.schemas.filters import BySearchableFilter, RemoveGeometryFilter
from c3nav.mapdata.schemas.model_base import AnyLocationID, AnyPositionID, CustomLocationID from c3nav.mapdata.schemas.model_base import AnyLocationID, AnyPositionID, CustomLocationID
from c3nav.mapdata.schemas.models import (AnyPositionStatusSchema, FullListableLocationSchema, FullLocationSchema, from c3nav.mapdata.schemas.models import (AnyPositionStatusSchema, FullListableLocationSchema, FullLocationSchema,
LocationDisplay, SlimListableLocationSchema, SlimLocationSchema, LocationDisplay, ProjectionPipelineSchema, ProjectionSchema,
all_location_definitions, listable_location_definitions) SlimListableLocationSchema, SlimLocationSchema, all_location_definitions,
listable_location_definitions)
from c3nav.mapdata.schemas.responses import LocationGeometry, WithBoundsSchema from c3nav.mapdata.schemas.responses import LocationGeometry, WithBoundsSchema
from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, get_location_by_slug_for_request, from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, get_location_by_slug_for_request,
searchable_locations_for_request, visible_locations_for_request) searchable_locations_for_request, visible_locations_for_request)
@ -310,3 +312,20 @@ def set_position(request, position_id: AnyPositionID, update: UpdatePositionSche
location.save() location.save()
return location.serialize_position() return location.serialize_position()
@map_api_router.get('/projection/', summary='get proj4 string',
description="get proj4 string for converting WGS84 coordinates to c3nva coordinates",
response={200: Union[ProjectionSchema, ProjectionPipelineSchema], **auth_responses})
def get_projection(request):
obj = {
"pipeline": settings.PROJECTION_TRANSFORMER_STRING
}
if True:
obj.update({
'proj4': settings.PROJECTION_PROJ4,
'zero_point': settings.PROJECTION_ZERO_POINT,
'rotation': settings.PROJECTION_ROTATION,
'rotation_matrix': settings.PROJECTION_ROTATION_MATRIX,
})
return obj

View file

@ -793,3 +793,46 @@ AnyPositionStatusSchema = Annotated[
], ],
Discriminator("available"), Discriminator("available"),
] ]
class ProjectionPipelineSchema(BaseSchema):
pipeline: Union[
Annotated[NonEmptyStr, APIField(title='proj4 string')],
Annotated[None, APIField(title='null', description='projection not available')]
] = APIField(
title='proj4 string',
description='proj4 string for converting WGS84 coordinates to c3nav coordinates if available',
example='+proj=utm +zone=33 +ellps=GRS80 +units=m +no_defs'
)
class ProjectionSchema(ProjectionPipelineSchema):
proj4: NonEmptyStr = APIField(
title='proj4 string',
description='proj4 string for converting WGS84 coordinates to c3nav coordinates without offset and rotation',
example='+proj=utm +zone=33 +ellps=GRS80 +units=m +no_defs'
)
zero_point: tuple[float, float] = APIField(
title='zero point',
description='coordinates of the zero point of the c3nav coordinate system',
example=(0.0, 0.0),
)
rotation: float = APIField(
title='rotation',
description='rotational offset of the c3nav coordinate system',
example=0.0,
)
rotation_matrix: Optional[tuple[
float, float, float, float,
float, float, float, float,
float, float, float, float,
float, float, float, float,
]] = APIField(
title='rotation matrix',
description='rotation matrix for rotational offset of the c3nav coordinate system',
example=[
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]
)

View file

@ -1,4 +1,5 @@
# c3nav settings, mostly taken from the pretix project # c3nav settings, mostly taken from the pretix project
import math
import os import os
import re import re
import string import string
@ -12,6 +13,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.dateparse import parse_duration from django.utils.dateparse import parse_duration
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from pyproj import Proj, Transformer
from c3nav import __version__ as c3nav_version from c3nav import __version__ as c3nav_version
from c3nav.utils.config import C3navConfigParser from c3nav.utils.config import C3navConfigParser
@ -606,6 +608,68 @@ BASE_THEME = {
WIFI_SSIDS = [n for n in config.get('c3nav', 'wifi_ssids', fallback='').split(',') if n] WIFI_SSIDS = [n for n in config.get('c3nav', 'wifi_ssids', fallback='').split(',') if n]
# Projection
PROJECTION_PROJ4 = config.get('projection', 'proj4', fallback=None)
PROJECTION_ZERO_POINT = config.get('projection', 'zero_point', fallback=None)
PROJECTION_ZERO_POINT_IS_WGS84 = '°' in PROJECTION_ZERO_POINT if PROJECTION_ZERO_POINT else False
PROJECTION_ROTATION = config.getfloat('projection', 'rotation', fallback=0.0)
PROJECTION_ROTATION_MATRIX = config.get('projection', 'rotation_matrix', fallback=None)
PROJECTION_TRANSFORMER: Optional[Transformer] = None
PROJECTION_TRANSFORMER_STRING: Optional[str] = None
if PROJECTION_PROJ4:
if '+units=m' not in PROJECTION_PROJ4:
PROJECTION_PROJ4 += ' +units=m'
PROJECTION_TRANSFORMER_STRING = re.sub(r'\s?\+no_defs', '', PROJECTION_PROJ4)
if (PROJECTION_ZERO_POINT or PROJECTION_ROTATION) and 'pipeline' not in PROJECTION_TRANSFORMER_STRING:
PROJECTION_TRANSFORMER_STRING = f'+proj=pipeline +step {PROJECTION_TRANSFORMER_STRING}'
if PROJECTION_ZERO_POINT:
PROJECTION_ZERO_POINT = tuple((float(i) for i in PROJECTION_ZERO_POINT.split(',')))
if len(PROJECTION_ZERO_POINT) != 2:
raise ImproperlyConfigured(f'invalid projection zero point "{PROJECTION_ZERO_POINT!r}"')
if PROJECTION_ZERO_POINT_IS_WGS84:
PROJECTION_ZERO_POINT = Proj.from_pipeline(PROJECTION_PROJ4).transform(PROJECTION_ZERO_POINT[0],
PROJECTION_ZERO_POINT[1])
PROJECTION_TRANSFORMER_STRING += (f' +step +proj=affine +xoff=-{PROJECTION_ZERO_POINT[0]} '
f'+yoff=-{PROJECTION_ZERO_POINT[1]}')
if PROJECTION_ROTATION != 0:
PROJECTION_ROTATION_MATRIX = (
math.cos(math.radians(PROJECTION_ROTATION)), math.sin(math.radians(PROJECTION_ROTATION)), 0, 0,
-math.sin(math.radians(PROJECTION_ROTATION)), math.cos(math.radians(PROJECTION_ROTATION)), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
)
elif PROJECTION_ROTATION_MATRIX:
PROJECTION_ROTATION_MATRIX = tuple((float(i) for i in PROJECTION_ROTATION_MATRIX.split(',')))
if len(PROJECTION_ROTATION_MATRIX) != 16:
raise ImproperlyConfigured(f'invalid rotation matrix "{PROJECTION_ROTATION_MATRIX!r}"')
if PROJECTION_ROTATION_MATRIX:
PROJECTION_TRANSFORMER_STRING += (
f' +step +proj=affine '
f'+s11={PROJECTION_ROTATION_MATRIX[0]} +s12={PROJECTION_ROTATION_MATRIX[1]}'
)
if PROJECTION_ROTATION_MATRIX[2] != 0:
PROJECTION_TRANSFORMER_STRING += f' +s13={PROJECTION_ROTATION_MATRIX[2]}'
PROJECTION_TRANSFORMER_STRING += f' +s21={PROJECTION_ROTATION_MATRIX[4]} +s22={PROJECTION_ROTATION_MATRIX[5]}'
if PROJECTION_ROTATION_MATRIX[6] != 0:
PROJECTION_TRANSFORMER_STRING += ' +s23={PROJECTION_ROTATION_MATRIX[6]}'
if PROJECTION_ROTATION_MATRIX[8] != 0:
PROJECTION_TRANSFORMER_STRING += f' +s31={PROJECTION_ROTATION_MATRIX[8]}'
if PROJECTION_ROTATION_MATRIX[9] != 0:
PROJECTION_TRANSFORMER_STRING += f' +s32={PROJECTION_ROTATION_MATRIX[9]}'
if PROJECTION_ROTATION_MATRIX[10] != 1:
PROJECTION_TRANSFORMER_STRING += f' +s33={PROJECTION_ROTATION_MATRIX[10]}'
if PROJECTION_ROTATION_MATRIX[15] != 1:
PROJECTION_TRANSFORMER_STRING += f' +tscale={PROJECTION_ROTATION_MATRIX[15]}'
PROJECTION_TRANSFORMER_STRING += ' +no_defs'
PROJECTION_TRANSFORMER = Proj.from_pipeline(PROJECTION_TRANSFORMER_STRING)
USER_REGISTRATION = config.getboolean('c3nav', 'user_registration', fallback=True) USER_REGISTRATION = config.getboolean('c3nav', 'user_registration', fallback=True)
INTERNAL_IPS = ('127.0.0.1', '::1') INTERNAL_IPS = ('127.0.0.1', '::1')

View file

@ -20,3 +20,4 @@ django_libsass==0.9
channels==4.0.0 channels==4.0.0
daphne==4.1.0 daphne==4.1.0
pyzstd==0.15.9 pyzstd==0.15.9
pyproj==3.6.1