split feature model – beginnings (still missing: API, Forms, Editor)

This commit is contained in:
Laura Klünder 2016-10-12 15:25:00 +02:00
parent 55a7e20df1
commit 0036b27057
15 changed files with 238 additions and 156 deletions

View file

@ -1,4 +1,4 @@
from .feature import Feature, FEATURE_TYPES # noqa
from .features import Feature, FEATURE_TYPES # noqa
from .level import Level # noqa
from .package import Package # noqa
from .source import Source # noqa

View 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')

View file

@ -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)))
))

View 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'

View file

@ -1,8 +1,10 @@
from django.db import models
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)
"""
@ -12,24 +14,29 @@ class Level(models.Model):
package = models.ForeignKey('mapdata.Package', on_delete=models.CASCADE, related_name='levels',
verbose_name=_('map package'))
path_regex = r'^levels/'
class Meta:
verbose_name = _('Level')
verbose_name_plural = _('Levels')
default_related_name = 'levels'
def tofilename(self):
return 'levels/%s.json' % self.name
@classmethod
def fromfile(cls, data, file_path):
kwargs = super().fromfile(data, file_path)
if 'altitude' not in data:
raise ValueError('missing altitude.')
if not isinstance(data['altitude'], (int, float)):
raise ValueError('altitude has to be int or float.')
return {
'altitude': data['altitude'],
}
kwargs['altitude'] = data['altitude']
return kwargs
def tofile(self):
return {
'altitude': float(self.altitude)
}
result = super().tofile()
result['altitude'] = float(self.altitude)
return result

View file

@ -22,7 +22,17 @@ class Package(models.Model):
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
def package(self):

View file

@ -1,21 +1,22 @@
from django.db import models
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
"""
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)
left = models.DecimalField(_('left 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)
path_regex = r'^sources/'
class Meta:
verbose_name = _('Source')
verbose_name_plural = _('Sources')
default_related_name = 'sources'
@classmethod
def max_bounds(cls):
@ -28,12 +29,9 @@ class Source(models.Model):
def bounds(self):
return (float(self.bottom), float(self.left)), (float(self.top), float(self.right))
def tofilename(self):
return 'sources/%s.json' % self.name
@classmethod
def fromfile(cls, data, file_path):
kwargs = {}
kwargs = super().fromfile(data, file_path)
if 'bounds' not in data:
raise ValueError('missing bounds.')
@ -50,6 +48,6 @@ class Source(models.Model):
return kwargs
def tofile(self):
return {
'bounds': ((float(self.bottom), float(self.left)), (float(self.top), float(self.right)))
}
result = super().tofile()
result['bounds'] = ((float(self.bottom), float(self.left)), (float(self.top), float(self.right)))
return result