split feature model – beginnings (still missing: API, Forms, Editor)
This commit is contained in:
parent
55a7e20df1
commit
0036b27057
15 changed files with 238 additions and 156 deletions
|
@ -15,7 +15,7 @@ router.register(r'levels', LevelViewSet)
|
||||||
router.register(r'packages', PackageViewSet)
|
router.register(r'packages', PackageViewSet)
|
||||||
router.register(r'sources', SourceViewSet)
|
router.register(r'sources', SourceViewSet)
|
||||||
router.register(r'featuretypes', FeatureTypeViewSet, base_name='featuretype')
|
router.register(r'featuretypes', FeatureTypeViewSet, base_name='featuretype')
|
||||||
router.register(r'features', FeatureViewSet)
|
router.register(r'features', FeatureViewSet, base_name='feature')
|
||||||
router.register(r'hosters', HosterViewSet, base_name='hoster')
|
router.register(r'hosters', HosterViewSet, base_name='hoster')
|
||||||
router.register(r'submittasks', SubmitTaskViewSet, base_name='submittask')
|
router.register(r'submittasks', SubmitTaskViewSet, base_name='submittask')
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ from django.core.signing import BadSignature
|
||||||
from django.http.response import Http404
|
from django.http.response import Http404
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
|
||||||
from c3nav.editor.forms import FeatureForm
|
# from c3nav.editor.forms import FeatureForm
|
||||||
from c3nav.editor.hosters import get_hoster_for_package, hosters
|
from c3nav.editor.hosters import get_hoster_for_package, hosters
|
||||||
from c3nav.mapdata.models.feature import FEATURE_TYPES, Feature
|
from c3nav.mapdata.models.features import FEATURE_TYPES, Feature
|
||||||
from c3nav.mapdata.models.package import Package
|
from c3nav.mapdata.models.package import Package
|
||||||
from c3nav.mapdata.packageio.write import json_encode
|
from c3nav.mapdata.packageio.write import json_encode
|
||||||
from c3nav.mapdata.permissions import can_access_package
|
from c3nav.mapdata.permissions import can_access_package
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
|
@ -8,7 +9,8 @@ from rest_framework.decorators import detail_route
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
||||||
|
|
||||||
from c3nav.mapdata.models import FEATURE_TYPES, Feature, Level, Package, Source
|
from c3nav.mapdata.models import FEATURE_TYPES, Level, Package, Source
|
||||||
|
from c3nav.mapdata.models.features import Feature
|
||||||
from c3nav.mapdata.permissions import filter_source_queryset
|
from c3nav.mapdata.permissions import filter_source_queryset
|
||||||
from c3nav.mapdata.serializers import (FeatureSerializer, FeatureTypeSerializer, LevelSerializer, PackageSerializer,
|
from c3nav.mapdata.serializers import (FeatureSerializer, FeatureTypeSerializer, LevelSerializer, PackageSerializer,
|
||||||
SourceSerializer)
|
SourceSerializer)
|
||||||
|
@ -89,7 +91,15 @@ class FeatureViewSet(ReadOnlyModelViewSet):
|
||||||
"""
|
"""
|
||||||
List and retrieve map features you have access to
|
List and retrieve map features you have access to
|
||||||
"""
|
"""
|
||||||
queryset = Feature.objects.all()
|
model = Feature
|
||||||
|
base_name = 'feature'
|
||||||
serializer_class = FeatureSerializer
|
serializer_class = FeatureSerializer
|
||||||
lookup_field = 'name'
|
lookup_field = 'name'
|
||||||
lookup_value_regex = '[^/]+'
|
lookup_value_regex = '[^/]+'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
querysets = []
|
||||||
|
for name, model in FEATURE_TYPES.items():
|
||||||
|
querysets.append(model.objects.all())
|
||||||
|
return chain(*querysets)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.1 on 2016-10-11 14:00
|
# Generated by Django 1.10.1 on 2016-10-12 12:12
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import c3nav.mapdata.fields
|
import c3nav.mapdata.fields
|
||||||
|
@ -16,14 +16,17 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Feature',
|
name='Inside',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.SlugField(unique=True, verbose_name='feature identifier')),
|
('name', models.SlugField(unique=True, verbose_name='name')),
|
||||||
('feature_type', models.CharField(choices=[('building', 'Building'), ('room', 'Room'), ('outside', 'Outside Area'), ('obstacle', 'Obstacle')], max_length=50)),
|
|
||||||
('titles', c3nav.mapdata.fields.JSONField()),
|
|
||||||
('geometry', c3nav.mapdata.fields.GeometryField()),
|
('geometry', c3nav.mapdata.fields.GeometryField()),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'default_related_name': 'insides',
|
||||||
|
'verbose_name': 'Inside Area',
|
||||||
|
'verbose_name_plural': 'Inside Areas',
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Level',
|
name='Level',
|
||||||
|
@ -32,6 +35,9 @@ class Migration(migrations.Migration):
|
||||||
('name', models.SlugField(help_text='Usually just an integer (e.g. -1, 0, 1, 2)', unique=True, verbose_name='level name')),
|
('name', models.SlugField(help_text='Usually just an integer (e.g. -1, 0, 1, 2)', unique=True, verbose_name='level name')),
|
||||||
('altitude', models.DecimalField(decimal_places=2, max_digits=6, null=True, verbose_name='level altitude')),
|
('altitude', models.DecimalField(decimal_places=2, max_digits=6, null=True, verbose_name='level altitude')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Package',
|
name='Package',
|
||||||
|
@ -47,6 +53,24 @@ class Migration(migrations.Migration):
|
||||||
('directory', models.CharField(max_length=100, verbose_name='folder name')),
|
('directory', models.CharField(max_length=100, verbose_name='folder name')),
|
||||||
('depends', models.ManyToManyField(to='mapdata.Package')),
|
('depends', models.ManyToManyField(to='mapdata.Package')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Room',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.SlugField(unique=True, verbose_name='name')),
|
||||||
|
('geometry', c3nav.mapdata.fields.GeometryField()),
|
||||||
|
('level', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rooms', to='mapdata.Level', verbose_name='level')),
|
||||||
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rooms', to='mapdata.Package', verbose_name='map package')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'default_related_name': 'rooms',
|
||||||
|
'verbose_name': 'Room',
|
||||||
|
'verbose_name_plural': 'Rooms',
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Source',
|
name='Source',
|
||||||
|
@ -59,6 +83,9 @@ class Migration(migrations.Migration):
|
||||||
('right', models.DecimalField(decimal_places=2, max_digits=6, verbose_name='right coordinate')),
|
('right', models.DecimalField(decimal_places=2, max_digits=6, verbose_name='right coordinate')),
|
||||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sources', to='mapdata.Package', verbose_name='map package')),
|
('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sources', to='mapdata.Package', verbose_name='map package')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='level',
|
model_name='level',
|
||||||
|
@ -66,13 +93,13 @@ class Migration(migrations.Migration):
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='levels', to='mapdata.Package', verbose_name='map package'),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='levels', to='mapdata.Package', verbose_name='map package'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='feature',
|
model_name='inside',
|
||||||
name='level',
|
name='level',
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='features', to='mapdata.Level', verbose_name='level'),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='insides', to='mapdata.Level', verbose_name='level'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='feature',
|
model_name='inside',
|
||||||
name='package',
|
name='package',
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='features', to='mapdata.Package', verbose_name='map package'),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='insides', to='mapdata.Package', verbose_name='map package'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from .feature import Feature, FEATURE_TYPES # noqa
|
from .features import Feature, FEATURE_TYPES # noqa
|
||||||
from .level import Level # noqa
|
from .level import Level # noqa
|
||||||
from .package import Package # noqa
|
from .package import Package # noqa
|
||||||
from .source import Source # noqa
|
from .source import Source # noqa
|
||||||
|
|
33
src/c3nav/mapdata/models/base.py
Normal file
33
src/c3nav/mapdata/models/base.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class MapdataModel(models.Model):
|
||||||
|
name = models.SlugField(_('Name'), max_length=50)
|
||||||
|
package = models.ForeignKey('mapdata.Package', on_delete=models.CASCADE, verbose_name=_('map package'))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_path_prefix(cls):
|
||||||
|
return cls._meta.default_related_name + '/'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_path_regex(cls):
|
||||||
|
return r'^' + cls.get_path_prefix()
|
||||||
|
|
||||||
|
def get_filename(self):
|
||||||
|
return self._meta.default_related_name + '/' + self.name + '.json'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromfile(cls, data, file_path):
|
||||||
|
kwargs = {}
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def tofile(self):
|
||||||
|
return OrderedDict()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
unique_together = ('package', 'name')
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
import os
|
|
||||||
from collections import OrderedDict, namedtuple
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.utils.translation import activate, get_language
|
|
||||||
from shapely.geometry import mapping, shape
|
|
||||||
|
|
||||||
from c3nav.mapdata.fields import GeometryField, JSONField
|
|
||||||
from c3nav.mapdata.utils import format_geojson
|
|
||||||
|
|
||||||
|
|
||||||
class FeatureType(namedtuple('FeatureType', ('name', 'title', 'title_plural', 'geomtype', 'color'))):
|
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__()
|
|
||||||
FEATURE_TYPES[self.name] = self
|
|
||||||
|
|
||||||
@property
|
|
||||||
def title_en(self):
|
|
||||||
language = get_language()
|
|
||||||
activate('en')
|
|
||||||
title = str(self.title)
|
|
||||||
activate(language)
|
|
||||||
return title
|
|
||||||
|
|
||||||
|
|
||||||
FEATURE_TYPES = OrderedDict()
|
|
||||||
FeatureType('building', _('Building'), _('Buildings'), 'polygon', '#333333')
|
|
||||||
FeatureType('room', _('Room'), _('Rooms'), 'polygon', '#FFFFFF')
|
|
||||||
FeatureType('outside', _('Outside Area'), _('Outside Areas'), 'polygon', '#FFFFFF')
|
|
||||||
FeatureType('obstacle', _('Obstacle'), _('Obstacles'), 'polygon', '#999999')
|
|
||||||
|
|
||||||
|
|
||||||
# FeatureType('door', _('Door'), 'polygon', '#FF00FF')
|
|
||||||
# FeatureType('step', _('Step'), 'polyline', '#FF0000')
|
|
||||||
# FeatureType('elevator', _('Elevator'), 'polygon', '#99CC00')
|
|
||||||
|
|
||||||
|
|
||||||
class Feature(models.Model):
|
|
||||||
"""
|
|
||||||
A map feature
|
|
||||||
"""
|
|
||||||
TYPES = tuple((name, t.title) for name, t in FEATURE_TYPES.items())
|
|
||||||
|
|
||||||
name = models.SlugField(_('feature identifier'), unique=True, max_length=50)
|
|
||||||
package = models.ForeignKey('mapdata.Package', on_delete=models.CASCADE, related_name='features',
|
|
||||||
verbose_name=_('map package'))
|
|
||||||
feature_type = models.CharField(max_length=50, choices=TYPES)
|
|
||||||
level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, related_name='features',
|
|
||||||
verbose_name=_('level'))
|
|
||||||
titles = JSONField()
|
|
||||||
geometry = GeometryField()
|
|
||||||
|
|
||||||
path_regex = r'^features/('+'|'.join(name for name, title in TYPES)+')/'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def title(self):
|
|
||||||
lang = get_language()
|
|
||||||
if lang in self.titles:
|
|
||||||
return self.titles[lang]
|
|
||||||
return next(iter(self.titles.values())) if self.titles else self.name
|
|
||||||
|
|
||||||
def tofilename(self):
|
|
||||||
return 'features/%s/%s.json' % (self.feature_type, self.name)
|
|
||||||
|
|
||||||
def get_feature_type(self):
|
|
||||||
return FEATURE_TYPES[self.feature_type]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def fromfile(cls, data, file_path):
|
|
||||||
kwargs = {}
|
|
||||||
kwargs['feature_type'] = file_path.split(os.path.sep)[1]
|
|
||||||
|
|
||||||
if 'geometry' not in data:
|
|
||||||
raise ValueError('missing geometry.')
|
|
||||||
try:
|
|
||||||
kwargs['geometry'] = shape(data['geometry'])
|
|
||||||
except:
|
|
||||||
raise ValueError(_('Invalid GeoJSON.'))
|
|
||||||
|
|
||||||
if 'level' not in data:
|
|
||||||
raise ValueError('missing level.')
|
|
||||||
kwargs['level'] = data['level']
|
|
||||||
|
|
||||||
if 'titles' not in data:
|
|
||||||
raise ValueError('missing titles.')
|
|
||||||
titles = data['titles']
|
|
||||||
if not isinstance(titles, dict):
|
|
||||||
raise ValueError('Invalid titles format.')
|
|
||||||
if any(not isinstance(lang, str) for lang in titles.keys()):
|
|
||||||
raise ValueError('titles: All languages have to be strings.')
|
|
||||||
if any(not isinstance(title, str) for title in titles.values()):
|
|
||||||
raise ValueError('titles: All titles have to be strings.')
|
|
||||||
if any(not title for title in titles.values()):
|
|
||||||
raise ValueError('titles: Titles must not be empty strings.')
|
|
||||||
kwargs['titles'] = titles
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
def tofile(self):
|
|
||||||
return OrderedDict((
|
|
||||||
('titles', OrderedDict(sorted(self.titles.items()))),
|
|
||||||
('level', self.level.name),
|
|
||||||
('geometry', format_geojson(mapping(self.geometry)))
|
|
||||||
))
|
|
81
src/c3nav/mapdata/models/features.py
Normal file
81
src/c3nav/mapdata/models/features.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import os
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from shapely.geometry.geo import shape, mapping
|
||||||
|
|
||||||
|
from c3nav.mapdata.fields import GeometryField
|
||||||
|
from c3nav.mapdata.models.base import MapdataModel
|
||||||
|
from c3nav.mapdata.utils import format_geojson
|
||||||
|
|
||||||
|
|
||||||
|
FEATURE_TYPES = OrderedDict()
|
||||||
|
|
||||||
|
def register_featuretype(cls):
|
||||||
|
FEATURE_TYPES[cls.__name__.lower()] = cls
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
class Feature(MapdataModel):
|
||||||
|
"""
|
||||||
|
A map feature
|
||||||
|
"""
|
||||||
|
level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, verbose_name=_('level'))
|
||||||
|
geometry = GeometryField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromfile(cls, data, file_path):
|
||||||
|
kwargs = super().fromfile(data, file_path)
|
||||||
|
|
||||||
|
if 'geometry' not in data:
|
||||||
|
raise ValueError('missing geometry.')
|
||||||
|
try:
|
||||||
|
kwargs['geometry'] = shape(data['geometry'])
|
||||||
|
except:
|
||||||
|
raise ValueError(_('Invalid GeoJSON.'))
|
||||||
|
|
||||||
|
if 'level' not in data:
|
||||||
|
raise ValueError('missing level.')
|
||||||
|
kwargs['level'] = data['level']
|
||||||
|
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def tofile(self):
|
||||||
|
result = super().tofile()
|
||||||
|
result['level'] = self.level.name
|
||||||
|
result['geometry'] = format_geojson(mapping(self.geometry))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@register_featuretype
|
||||||
|
class Inside(Feature):
|
||||||
|
"""
|
||||||
|
The outline of a building on a specific level
|
||||||
|
"""
|
||||||
|
geomtype = 'polygon'
|
||||||
|
color = '#333333'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Inside Area')
|
||||||
|
verbose_name_plural = _('Inside Areas')
|
||||||
|
default_related_name = 'insides'
|
||||||
|
|
||||||
|
|
||||||
|
@register_featuretype
|
||||||
|
class Room(Feature):
|
||||||
|
"""
|
||||||
|
A room inside
|
||||||
|
"""
|
||||||
|
geomtype = 'polygon'
|
||||||
|
color = '#FFFFFF'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Room')
|
||||||
|
verbose_name_plural = _('Rooms')
|
||||||
|
default_related_name = 'rooms'
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from c3nav.mapdata.models.base import MapdataModel
|
||||||
|
|
||||||
class Level(models.Model):
|
|
||||||
|
class Level(MapdataModel):
|
||||||
"""
|
"""
|
||||||
A map level (-1, 0, 1, 2…)
|
A map level (-1, 0, 1, 2…)
|
||||||
"""
|
"""
|
||||||
|
@ -12,24 +14,29 @@ class Level(models.Model):
|
||||||
package = models.ForeignKey('mapdata.Package', on_delete=models.CASCADE, related_name='levels',
|
package = models.ForeignKey('mapdata.Package', on_delete=models.CASCADE, related_name='levels',
|
||||||
verbose_name=_('map package'))
|
verbose_name=_('map package'))
|
||||||
|
|
||||||
path_regex = r'^levels/'
|
class Meta:
|
||||||
|
verbose_name = _('Level')
|
||||||
|
verbose_name_plural = _('Levels')
|
||||||
|
default_related_name = 'levels'
|
||||||
|
|
||||||
def tofilename(self):
|
def tofilename(self):
|
||||||
return 'levels/%s.json' % self.name
|
return 'levels/%s.json' % self.name
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromfile(cls, data, file_path):
|
def fromfile(cls, data, file_path):
|
||||||
|
kwargs = super().fromfile(data, file_path)
|
||||||
|
|
||||||
if 'altitude' not in data:
|
if 'altitude' not in data:
|
||||||
raise ValueError('missing altitude.')
|
raise ValueError('missing altitude.')
|
||||||
|
|
||||||
if not isinstance(data['altitude'], (int, float)):
|
if not isinstance(data['altitude'], (int, float)):
|
||||||
raise ValueError('altitude has to be int or float.')
|
raise ValueError('altitude has to be int or float.')
|
||||||
|
|
||||||
return {
|
kwargs['altitude'] = data['altitude']
|
||||||
'altitude': data['altitude'],
|
|
||||||
}
|
return kwargs
|
||||||
|
|
||||||
def tofile(self):
|
def tofile(self):
|
||||||
return {
|
result = super().tofile()
|
||||||
'altitude': float(self.altitude)
|
result['altitude'] = float(self.altitude)
|
||||||
}
|
return result
|
||||||
|
|
|
@ -22,7 +22,17 @@ class Package(models.Model):
|
||||||
|
|
||||||
directory = models.CharField(_('folder name'), max_length=100)
|
directory = models.CharField(_('folder name'), max_length=100)
|
||||||
|
|
||||||
path_regex = r'^package.json$'
|
class Meta:
|
||||||
|
verbose_name = _('Map Package')
|
||||||
|
verbose_name_plural = _('Map Packages')
|
||||||
|
default_related_name = 'packages'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_path_regex(cls):
|
||||||
|
return '^package.json$'
|
||||||
|
|
||||||
|
def get_filename(self):
|
||||||
|
return 'package.json'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def package(self):
|
def package(self):
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from c3nav.mapdata.models.base import MapdataModel
|
||||||
|
|
||||||
class Source(models.Model):
|
|
||||||
|
class Source(MapdataModel):
|
||||||
"""
|
"""
|
||||||
A map source, images of levels that can be useful as backgrounds for the map editor
|
A map source, images of levels that can be useful as backgrounds for the map editor
|
||||||
"""
|
"""
|
||||||
name = models.SlugField(_('source name'), unique=True, max_length=50)
|
|
||||||
package = models.ForeignKey('mapdata.Package', on_delete=models.CASCADE, related_name='sources',
|
|
||||||
verbose_name=_('map package'))
|
|
||||||
|
|
||||||
bottom = models.DecimalField(_('bottom coordinate'), max_digits=6, decimal_places=2)
|
bottom = models.DecimalField(_('bottom coordinate'), max_digits=6, decimal_places=2)
|
||||||
left = models.DecimalField(_('left coordinate'), max_digits=6, decimal_places=2)
|
left = models.DecimalField(_('left coordinate'), max_digits=6, decimal_places=2)
|
||||||
top = models.DecimalField(_('top coordinate'), max_digits=6, decimal_places=2)
|
top = models.DecimalField(_('top coordinate'), max_digits=6, decimal_places=2)
|
||||||
right = models.DecimalField(_('right coordinate'), max_digits=6, decimal_places=2)
|
right = models.DecimalField(_('right coordinate'), max_digits=6, decimal_places=2)
|
||||||
|
|
||||||
path_regex = r'^sources/'
|
class Meta:
|
||||||
|
verbose_name = _('Source')
|
||||||
|
verbose_name_plural = _('Sources')
|
||||||
|
default_related_name = 'sources'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def max_bounds(cls):
|
def max_bounds(cls):
|
||||||
|
@ -28,12 +29,9 @@ class Source(models.Model):
|
||||||
def bounds(self):
|
def bounds(self):
|
||||||
return (float(self.bottom), float(self.left)), (float(self.top), float(self.right))
|
return (float(self.bottom), float(self.left)), (float(self.top), float(self.right))
|
||||||
|
|
||||||
def tofilename(self):
|
|
||||||
return 'sources/%s.json' % self.name
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromfile(cls, data, file_path):
|
def fromfile(cls, data, file_path):
|
||||||
kwargs = {}
|
kwargs = super().fromfile(data, file_path)
|
||||||
|
|
||||||
if 'bounds' not in data:
|
if 'bounds' not in data:
|
||||||
raise ValueError('missing bounds.')
|
raise ValueError('missing bounds.')
|
||||||
|
@ -50,6 +48,6 @@ class Source(models.Model):
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def tofile(self):
|
def tofile(self):
|
||||||
return {
|
result = super().tofile()
|
||||||
'bounds': ((float(self.bottom), float(self.left)), (float(self.top), float(self.right)))
|
result['bounds'] = ((float(self.bottom), float(self.left)), (float(self.top), float(self.right)))
|
||||||
}
|
return result
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
from c3nav.mapdata.models import Feature, Level, Package, Source
|
from c3nav.mapdata.models import Level, Package, Source
|
||||||
|
from c3nav.mapdata.models.features import Inside, Room
|
||||||
|
|
||||||
ordered_models = (Package, Level, Source, Feature)
|
ordered_models = (Package, Level, Source, Inside, Room)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management import CommandError
|
from django.core.management import CommandError
|
||||||
|
@ -15,6 +16,8 @@ class MapdataReader:
|
||||||
self.content = {}
|
self.content = {}
|
||||||
self.package_names_by_dir = {}
|
self.package_names_by_dir = {}
|
||||||
self.saved_items = {model: {} for model in ordered_models}
|
self.saved_items = {model: {} for model in ordered_models}
|
||||||
|
self.path_regexes = OrderedDict((model, model.get_path_regex()) for model in ordered_models)
|
||||||
|
print(self.path_regexes)
|
||||||
|
|
||||||
def read_packages(self):
|
def read_packages(self):
|
||||||
print('Detecting Map Packages…')
|
print('Detecting Map Packages…')
|
||||||
|
@ -44,8 +47,8 @@ class MapdataReader:
|
||||||
file_path = os.path.join(package_dir, path, filename)
|
file_path = os.path.join(package_dir, path, filename)
|
||||||
relative_file_path = os.path.join(path, filename)
|
relative_file_path = os.path.join(path, filename)
|
||||||
print(file_path)
|
print(file_path)
|
||||||
for model in ordered_models:
|
for model, path_regex in self.path_regexes.items():
|
||||||
if re.search(model.path_regex, relative_file_path):
|
if re.search(path_regex, relative_file_path):
|
||||||
self._add_item(ReaderItem(self, package_dir, path, filename, model))
|
self._add_item(ReaderItem(self, package_dir, path, filename, model))
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -24,7 +24,7 @@ class MapdataWriter:
|
||||||
count = 0
|
count = 0
|
||||||
for model in ordered_models:
|
for model in ordered_models:
|
||||||
for obj in model.objects.all().order_by('name').prefetch_related():
|
for obj in model.objects.all().order_by('name').prefetch_related():
|
||||||
file_path = os.path.join(obj.package.directory, obj.tofilename())
|
file_path = os.path.join(obj.package.directory, obj.get_filename())
|
||||||
full_file_path = os.path.join(settings.MAP_ROOT, file_path)
|
full_file_path = os.path.join(settings.MAP_ROOT, file_path)
|
||||||
self.keep.add(file_path)
|
self.keep.add(file_path)
|
||||||
|
|
||||||
|
|
|
@ -60,18 +60,35 @@ class SourceSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class FeatureTypeSerializer(serializers.Serializer):
|
class FeatureTypeSerializer(serializers.Serializer):
|
||||||
name = serializers.CharField()
|
name = serializers.SerializerMethodField()
|
||||||
title = serializers.CharField()
|
title = serializers.SerializerMethodField()
|
||||||
title_plural = serializers.CharField()
|
title_plural = serializers.SerializerMethodField()
|
||||||
geomtype = serializers.CharField()
|
geomtype = serializers.CharField()
|
||||||
color = serializers.CharField()
|
color = serializers.CharField()
|
||||||
|
|
||||||
|
def get_name(self, obj):
|
||||||
|
return obj.__name__.lower()
|
||||||
|
|
||||||
class FeatureSerializer(serializers.ModelSerializer):
|
def get_title(self, obj):
|
||||||
level = serializers.SlugRelatedField(slug_field='name', read_only=True)
|
return str(obj._meta.verbose_name)
|
||||||
titles = serializers.JSONField()
|
|
||||||
|
def get_title_plural(self, obj):
|
||||||
|
return str(obj._meta.verbose_name_plural)
|
||||||
|
|
||||||
|
|
||||||
|
class FeatureSerializer(serializers.Serializer):
|
||||||
|
name = serializers.CharField()
|
||||||
|
feature_type = serializers.SerializerMethodField()
|
||||||
|
level = serializers.SerializerMethodField()
|
||||||
|
package = serializers.SerializerMethodField()
|
||||||
geometry = GeometryField()
|
geometry = GeometryField()
|
||||||
|
|
||||||
class Meta:
|
def get_feature_type(self, obj):
|
||||||
model = Feature
|
return obj.__class__.__name__.lower()
|
||||||
fields = ('name', 'title', 'feature_type', 'level', 'titles', 'package', 'geometry')
|
|
||||||
|
def get_level(self, obj):
|
||||||
|
return obj.level.name
|
||||||
|
|
||||||
|
def get_package(self, obj):
|
||||||
|
return obj.package.name
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue