implement new django-based models and loadmappkg command
This commit is contained in:
parent
66661209d2
commit
752b7d6d7d
14 changed files with 318 additions and 196 deletions
|
@ -10,7 +10,7 @@
|
|||
var map = L.map('mapeditor', {
|
||||
center: [120, 200],
|
||||
zoom: 2,
|
||||
maxBounds: [[0, 0], [{{ map.height }}, {{ map.width }}]],
|
||||
maxBounds: {{ bounds }},
|
||||
maxZoom: 10,
|
||||
minZoom: 1,
|
||||
crs: L.CRS.Simple,
|
||||
|
@ -19,10 +19,10 @@ var map = L.map('mapeditor', {
|
|||
});
|
||||
|
||||
// Add Source Layers
|
||||
{% for pkg in map.pkgs.values %}
|
||||
{% for pkg in packages %}
|
||||
L.control.layers([], {
|
||||
{% for source in pkg.sources %}
|
||||
"{{ source.name }}": L.imageOverlay('{% url 'map.source' filename=source.filename %}', {{ source.jsbounds }}),{% endfor %}
|
||||
{% for source in pkg.sources.all %}
|
||||
"{{ source.name }}": L.imageOverlay('{% url 'map.source' source=source.name %}', {{ source.jsbounds }}),{% endfor %}
|
||||
}).addTo(map);
|
||||
{% endfor %}
|
||||
|
||||
|
@ -33,10 +33,10 @@ L.LevelControl = L.Control.extend({
|
|||
},
|
||||
onAdd: function (map) {
|
||||
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-levels'), link;
|
||||
{% for level in map.levels reversed %}
|
||||
{% for level in levels reversed %}
|
||||
link = L.DomUtil.create('a', '{% if current_level == level %}current{% endif %}', container);
|
||||
link.href = '{% url "control.editor" level=level %}';
|
||||
link.innerHTML = '{{ level }}';
|
||||
link.href = '{% url "control.editor" level=level.name %}';
|
||||
link.innerHTML = '{{ level.name }}';
|
||||
{% endfor %}
|
||||
return container;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.http import Http404
|
||||
from django.shortcuts import redirect, render
|
||||
import json
|
||||
|
||||
from ..mapdata import mapmanager
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
|
||||
from ..mapdata.models import MapLevel, MapPackage, MapSource
|
||||
|
||||
|
||||
@staff_member_required
|
||||
|
@ -13,10 +14,12 @@ def dashboard(request):
|
|||
@staff_member_required
|
||||
def editor(request, level=None):
|
||||
if not level:
|
||||
return redirect('control.editor', level=mapmanager.levels[0])
|
||||
if level not in mapmanager.levels:
|
||||
raise Http404('Level does not exist')
|
||||
return redirect('control.editor', level=MapLevel.objects.first().name)
|
||||
|
||||
level = get_object_or_404(MapLevel, name=level)
|
||||
return render(request, 'control/editor.html', {
|
||||
'map': mapmanager,
|
||||
'bounds': json.dumps(MapSource.max_bounds()),
|
||||
'packages': MapPackage.objects.all(),
|
||||
'levels': MapLevel.objects.all(),
|
||||
'current_level': level,
|
||||
})
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
from .classes import MapManager
|
||||
|
||||
default_app_config = 'c3nav.mapdata.apps.MapdataConfig'
|
||||
|
||||
mapmanager = MapManager()
|
|
@ -1,25 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
from django.core.checks import Warning, register
|
||||
|
||||
from . import mapmanager
|
||||
|
||||
|
||||
@register()
|
||||
def has_map_data_check(app_configs, **kwargs):
|
||||
if not settings.MAP_DIRS:
|
||||
return [Warning(
|
||||
'There are no map data directories configured.',
|
||||
hint='Add mapdirs=/path/to/directory to your c3nav.cfg.',
|
||||
id='mapdata.W001',
|
||||
)]
|
||||
return []
|
||||
|
||||
|
||||
class MapdataConfig(AppConfig):
|
||||
name = 'c3nav.mapdata'
|
||||
verbose_name = 'map data manager'
|
||||
|
||||
def ready(self):
|
||||
for map_dir in settings.MAP_DIRS:
|
||||
mapmanager.add_map_dir(map_dir)
|
|
@ -1,76 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
class MapInitError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MapManager:
|
||||
def __init__(self):
|
||||
self.main_pkg = None
|
||||
self.pkgs = OrderedDict()
|
||||
self.levels = []
|
||||
self.sources_by_filename = OrderedDict()
|
||||
|
||||
def add_map_dir(self, path):
|
||||
pkg = MapDataPackage(path)
|
||||
if pkg.name in self.pkgs:
|
||||
raise MapInitError('Duplicate map package: '+pkg.name)
|
||||
|
||||
if pkg.extends is None:
|
||||
if self.main_pkg is not None:
|
||||
raise MapInitError('There can not be more than one root map package: tried to add '+pkg.name+', '
|
||||
'but '+self.main_pkg.name+' was there first.')
|
||||
self.main_pkg = pkg
|
||||
self.levels = pkg.levels
|
||||
self.width = pkg.width
|
||||
self.height = pkg.height
|
||||
else:
|
||||
if pkg.extends not in self.pkgs:
|
||||
raise MapInitError('map package'+pkg.name+' extends '+pkg.exends+', which was not imported '
|
||||
'beforehand.')
|
||||
|
||||
for source in pkg.sources:
|
||||
self.sources_by_filename[source.filename] = source
|
||||
|
||||
self.pkgs[pkg.name] = pkg
|
||||
|
||||
|
||||
class MapDataPackage:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
main_file = os.path.join(path, 'map.json')
|
||||
try:
|
||||
data = json.load(open(main_file))
|
||||
except FileNotFoundError:
|
||||
raise MapInitError(main_file+' not found')
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
raise MapInitError('Could not decode '+main_file+': '+str(e))
|
||||
|
||||
self.name = data.get('name')
|
||||
if self.name is None:
|
||||
raise MapInitError('Map package '+path+' has no name in map.json.')
|
||||
|
||||
self.extends = data.get('extends')
|
||||
|
||||
self.width = data.get('width')
|
||||
self.height = data.get('height')
|
||||
|
||||
self.sources = tuple(MapSource(self, source) for source in data.get('sources', []))
|
||||
|
||||
self.levels = data.get('levels')
|
||||
|
||||
|
||||
class MapSource:
|
||||
def __init__(self, pkg, data):
|
||||
self.name = data['name']
|
||||
self.filename = self.name+'.'+data['src'].split('.')[-1]
|
||||
self.src = os.path.join(pkg.path, data['src'])
|
||||
self.bounds = data['bounds']
|
||||
|
||||
@property
|
||||
def jsbounds(self):
|
||||
return json.dumps(self.bounds)
|
0
src/c3nav/mapdata/management/__init__.py
Normal file
0
src/c3nav/mapdata/management/__init__.py
Normal file
0
src/c3nav/mapdata/management/commands/__init__.py
Normal file
0
src/c3nav/mapdata/management/commands/__init__.py
Normal file
19
src/c3nav/mapdata/management/commands/loadmappkgs.py
Normal file
19
src/c3nav/mapdata/management/commands/loadmappkgs.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from ...packageio import MapPackagesIO
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Load the given map packages into the database'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('mappkgdir', nargs='+', type=str, help='map package directories')
|
||||
parser.add_argument('-y', action='store_const', const=True, default=False,
|
||||
help='don\'t ask for confirmation')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
with transaction.atomic():
|
||||
MapPackagesIO(options['mappkgdir']).update_to_db()
|
||||
print()
|
||||
if input('Confirm (y/N): ') != 'y':
|
||||
raise CommandError('Aborted.')
|
|
@ -1,7 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.9 on 2016-08-25 11:37
|
||||
# Generated by Django 1.9.9 on 2016-08-28 15:52
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import c3nav.mapdata.models
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import parler.models
|
||||
|
@ -16,72 +17,78 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Bounds',
|
||||
name='MapFeature',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('bottom', models.DecimalField(decimal_places=2, max_digits=6, verbose_name='bottom coordinate')),
|
||||
('left', models.DecimalField(decimal_places=2, max_digits=6, verbose_name='left coordinate')),
|
||||
('top', models.DecimalField(decimal_places=2, max_digits=6, verbose_name='top coordinate')),
|
||||
('right', models.DecimalField(decimal_places=2, max_digits=6, verbose_name='right coordinate')),
|
||||
('name', models.CharField(help_text='e.g. noc', max_length=50, unique=True, verbose_name='feature identifier')),
|
||||
('type', models.CharField(choices=[('building', 'Building'), ('room', 'Room'), ('obstacle', 'Obstacle')], max_length=50)),
|
||||
('geometry', models.TextField()),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(parler.models.TranslatableModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MapFeatureTranslation',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')),
|
||||
('title', models.CharField(max_length=50, verbose_name='package title')),
|
||||
('master', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='mapdata.MapFeature')),
|
||||
],
|
||||
options={
|
||||
'managed': True,
|
||||
'db_tablespace': '',
|
||||
'verbose_name': 'map feature Translation',
|
||||
'db_table': 'mapdata_mapfeature_translation',
|
||||
'default_permissions': (),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MapLevel',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Usually just an integer (e.g. -1, 0, 1, 2)', max_length=50, unique=True, verbose_name='level name')),
|
||||
('altitude', models.DecimalField(decimal_places=2, max_digits=6, null=True, verbose_name='level altitude')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(parler.models.TranslatableModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MapPackage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='e.g. de.c3nav.33c3.base', max_length=50, unique=True, verbose_name='package identifier')),
|
||||
('map', models.CharField(help_text='e.g. de.c3nav.33c3', max_length=50, verbose_name='map identifier')),
|
||||
('bounds', models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='mapdata.Bounds', verbose_name='bounds')),
|
||||
('bottom', models.DecimalField(decimal_places=2, max_digits=6, null=True, verbose_name='bottom coordinate')),
|
||||
('left', models.DecimalField(decimal_places=2, max_digits=6, null=True, verbose_name='left coordinate')),
|
||||
('top', models.DecimalField(decimal_places=2, max_digits=6, null=True, verbose_name='top coordinate')),
|
||||
('right', models.DecimalField(decimal_places=2, max_digits=6, null=True, verbose_name='right coordinate')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=(parler.models.TranslatableModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MapPackageTranslation',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')),
|
||||
('title', models.CharField(max_length=50, verbose_name='package title')),
|
||||
('master', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='mapdata.MapPackage')),
|
||||
],
|
||||
options={
|
||||
'db_tablespace': '',
|
||||
'verbose_name': 'map package Translation',
|
||||
'managed': True,
|
||||
'db_table': 'mapdata_mappackage_translation',
|
||||
'default_permissions': (),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MapSource',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.SlugField(unique=True, verbose_name='source name')),
|
||||
('image', models.FileField(upload_to='mapsources/', verbose_name='source image')),
|
||||
('bounds', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='mapdata.Bounds', verbose_name='bounds')),
|
||||
('package', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='sources', to='mapdata.MapPackage', verbose_name='map package')),
|
||||
('image', models.FileField(max_length=70, storage=c3nav.mapdata.models.MapSourceImageStorage(), upload_to=c3nav.mapdata.models.map_source_filename, verbose_name='source image')),
|
||||
('bottom', models.DecimalField(decimal_places=2, max_digits=6, verbose_name='bottom coordinate')),
|
||||
('left', models.DecimalField(decimal_places=2, max_digits=6, verbose_name='left coordinate')),
|
||||
('top', models.DecimalField(decimal_places=2, max_digits=6, verbose_name='top 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.MapPackage', verbose_name='map package')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='maplevel',
|
||||
name='package',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='levels', to='mapdata.MapPackage', verbose_name='map package'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='levels', to='mapdata.MapPackage', verbose_name='map package'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mapfeature',
|
||||
name='package',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='features', to='mapdata.MapPackage', verbose_name='map package'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='mappackagetranslation',
|
||||
name='mapfeaturetranslation',
|
||||
unique_together=set([('language_code', 'master')]),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,50 +1,122 @@
|
|||
from django.db import models
|
||||
import json
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models, transaction
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.dispatch import receiver
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
|
||||
from parler.models import TranslatedFields, TranslatableModel
|
||||
|
||||
|
||||
class Bounds(models.Model):
|
||||
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)
|
||||
|
||||
@property
|
||||
def __iter__(self):
|
||||
return iter(((self.bottom, self.left), (self.top, self.right)))
|
||||
|
||||
|
||||
class MapPackage(TranslatableModel):
|
||||
class MapPackage(models.Model):
|
||||
"""
|
||||
A c3nav map package
|
||||
"""
|
||||
name = models.CharField(_('package identifier'), unique=True, max_length=50,
|
||||
help_text=_('e.g. de.c3nav.33c3.base'))
|
||||
map = models.CharField(_('map identifier'), max_length=50, help_text=_('e.g. de.c3nav.33c3'))
|
||||
bounds = models.OneToOneField('Bounds', null=True, on_delete=models.PROTECT, verbose_name=_('bounds'))
|
||||
|
||||
translations = TranslatedFields(
|
||||
title=models.CharField(_('package title'), max_length=50),
|
||||
)
|
||||
bottom = models.DecimalField(_('bottom coordinate'), null=True, max_digits=6, decimal_places=2)
|
||||
left = models.DecimalField(_('left coordinate'), null=True, max_digits=6, decimal_places=2)
|
||||
top = models.DecimalField(_('top coordinate'), null=True, max_digits=6, decimal_places=2)
|
||||
right = models.DecimalField(_('right coordinate'), null=True, max_digits=6, decimal_places=2)
|
||||
|
||||
|
||||
class MapLevel(TranslatableModel):
|
||||
class MapLevel(models.Model):
|
||||
"""
|
||||
A map level (-1, 0, 1, 2…)
|
||||
"""
|
||||
name = models.CharField(_('level name'), max_length=50, unique=True,
|
||||
help_text=_('Usually just an integer (e.g. -1, 0, 1, 2)'))
|
||||
package = models.ForeignKey('MapPackage', on_delete=models.PROTECT, related_name='levels',
|
||||
altitude = models.DecimalField(_('level altitude'), null=True, max_digits=6, decimal_places=2)
|
||||
package = models.ForeignKey('MapPackage', on_delete=models.CASCADE, related_name='levels',
|
||||
verbose_name=_('map package'))
|
||||
|
||||
class Meta:
|
||||
ordering = ['altitude']
|
||||
|
||||
|
||||
class MapSourceImageStorage(FileSystemStorage):
|
||||
def get_available_name(self, name, *args, max_length=None, **kwargs):
|
||||
if self.exists(name):
|
||||
os.remove(os.path.join(settings.MEDIA_ROOT, name))
|
||||
return super().get_available_name(name, *args, max_length, **kwargs)
|
||||
|
||||
|
||||
def map_source_filename(instance, filename):
|
||||
return os.path.join('mapsources', '%s.%s' % (instance.name, filename.split('.')[-1]))
|
||||
|
||||
|
||||
class MapSource(models.Model):
|
||||
"""
|
||||
A map source, images of levels that can be useful as backgrounds for the map editor
|
||||
"""
|
||||
name = models.SlugField(_('source name'), max_length=50, unique=True)
|
||||
package = models.ForeignKey('MapPackage', on_delete=models.PROTECT, related_name='sources',
|
||||
package = models.ForeignKey('MapPackage', on_delete=models.CASCADE, related_name='sources',
|
||||
verbose_name=_('map package'))
|
||||
image = models.FileField(_('source image'), upload_to='mapsources/')
|
||||
bounds = models.OneToOneField('Bounds', on_delete=models.PROTECT, verbose_name=_('bounds'))
|
||||
|
||||
image = models.FileField(_('source image'), max_length=70,
|
||||
upload_to=map_source_filename, storage=MapSourceImageStorage())
|
||||
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def max_bounds(cls):
|
||||
result = cls.objects.all().aggregate(models.Min('bottom'), models.Min('left'),
|
||||
models.Max('top'), models.Max('right'))
|
||||
return ((float(result['bottom__min']), float(result['left__min'])),
|
||||
(float(result['top__max']), float(result['right__max'])))
|
||||
|
||||
@property
|
||||
def bounds(self):
|
||||
return ((self.bottom, self.left), (self.top, self.right))
|
||||
|
||||
@property
|
||||
def jsbounds(self):
|
||||
return json.dumps(((float(self.bottom), float(self.left)), (float(self.top), float(self.right))))
|
||||
|
||||
|
||||
@receiver(models.signals.post_delete, sender=MapSource)
|
||||
def delete_image_on_mapsource_delete(sender, instance, **kwargs):
|
||||
transaction.on_commit(lambda: instance.image.delete(save=False))
|
||||
|
||||
|
||||
@receiver(models.signals.pre_save, sender=MapSource)
|
||||
def delete_image_on_mapsource_change(sender, instance, **kwargs):
|
||||
if not instance.pk:
|
||||
return False
|
||||
|
||||
try:
|
||||
old_file = MapSource.objects.get(pk=instance.pk).image
|
||||
except MapSource.DoesNotExist:
|
||||
return False
|
||||
|
||||
new_file = instance.image
|
||||
|
||||
if map_source_filename(instance, new_file.name) != old_file.name:
|
||||
transaction.on_commit(lambda: old_file.delete(save=False))
|
||||
|
||||
|
||||
class MapFeature(TranslatableModel):
|
||||
"""
|
||||
A map feature
|
||||
"""
|
||||
TYPES = (
|
||||
('building', _('Building')),
|
||||
('room', _('Room')),
|
||||
('obstacle', _('Obstacle')),
|
||||
)
|
||||
|
||||
name = models.CharField(_('feature identifier'), unique=True, max_length=50, help_text=_('e.g. noc'))
|
||||
package = models.ForeignKey('MapPackage', on_delete=models.CASCADE, related_name='features',
|
||||
verbose_name=_('map package'))
|
||||
type = models.CharField(max_length=50, choices=TYPES)
|
||||
geometry = models.TextField()
|
||||
|
||||
translations = TranslatedFields(
|
||||
title=models.CharField(_('package title'), max_length=50),
|
||||
)
|
||||
|
|
132
src/c3nav/mapdata/packageio.py
Normal file
132
src/c3nav/mapdata/packageio.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from collections import OrderedDict
|
||||
from django.core.files import File
|
||||
|
||||
from django.core.management.base import CommandError
|
||||
|
||||
|
||||
class PackageIOError(CommandError):
|
||||
pass
|
||||
|
||||
|
||||
class MapPackagesIO():
|
||||
def __init__(self, directories):
|
||||
print('Opening Map Packages…')
|
||||
self.packages = OrderedDict()
|
||||
self.levels = OrderedDict()
|
||||
self.sources = OrderedDict()
|
||||
|
||||
for directory in directories:
|
||||
print('- '+directory)
|
||||
|
||||
try:
|
||||
package = json.load(open(os.path.join(directory, 'pkg.json')))
|
||||
except FileNotFoundError:
|
||||
raise PackageIOError('no pkg.json found in %s' % directory)
|
||||
|
||||
if package['name'] in self.packages:
|
||||
raise PackageIOError('Duplicate package name: %s' % package['name'])
|
||||
|
||||
if 'bounds' in package:
|
||||
self._validate_bounds(package['bounds'])
|
||||
|
||||
package['directory'] = directory
|
||||
self.packages[package['name']] = package
|
||||
|
||||
for level in package.get('levels', []):
|
||||
level = level.copy()
|
||||
if level['name'] in self.levels:
|
||||
raise PackageIOError('Duplicate level name: %s in packages %s and %s' %
|
||||
(level, self.levels[level]['name'], package['name']))
|
||||
|
||||
if not isinstance(level['altitude'], (int, float)):
|
||||
raise PackageIOError('levels: %s: altitude has to be int or float.' % level['name'])
|
||||
|
||||
level['package'] = package['name']
|
||||
self.levels[level['name']] = level
|
||||
|
||||
for source in package.get('sources', []):
|
||||
source = source.copy()
|
||||
if source['name'] in self.sources:
|
||||
raise PackageIOError('Duplicate source name: %s in packages %s and %s' %
|
||||
(source['name'], self.sources[source['name']]['name'], package['name']))
|
||||
|
||||
self._validate_bounds(source['bounds'], 'sources: %s: ' % source['name'])
|
||||
|
||||
source['filename'] = os.path.join(directory, source['src'])
|
||||
if not os.path.isfile(source['filename']):
|
||||
raise PackageIOError('Source file not found: '+source['filename'])
|
||||
|
||||
source['package'] = package['name']
|
||||
self.sources[source['name']] = source
|
||||
|
||||
def _validate_bounds(self, bounds, prefix=''):
|
||||
if len(bounds) != 2 or len(bounds[0]) != 2 or len(bounds[1]) != 2:
|
||||
raise PackageIOError(prefix+'Invalid bounds format.')
|
||||
if not all(isinstance(i, (float, int)) for i in sum(bounds, [])):
|
||||
raise PackageIOError(prefix+'All bounds coordinates have to be int or float.')
|
||||
if bounds[0][0] >= bounds[1][0] or bounds[0][1] >= bounds[1][1]:
|
||||
raise PackageIOError(prefix+'bounds: lower coordinate has to be first.')
|
||||
|
||||
def update_to_db(self):
|
||||
from .models import MapPackage, MapLevel, MapSource
|
||||
print('Updating Map database…')
|
||||
|
||||
# Add new Packages
|
||||
packages = {}
|
||||
print('- Updating packages…')
|
||||
for name, package in self.packages.items():
|
||||
bounds = package.get('bounds')
|
||||
defaults = {
|
||||
'bottom': bounds[0][0],
|
||||
'left': bounds[0][1],
|
||||
'top': bounds[1][0],
|
||||
'right': bounds[1][1],
|
||||
} if bounds else {}
|
||||
|
||||
package, created = MapPackage.objects.update_or_create(name=name, defaults=defaults)
|
||||
packages[name] = package
|
||||
if created:
|
||||
print('- Created package: '+name)
|
||||
|
||||
# Add new levels
|
||||
print('- Updating levels…')
|
||||
for name, level in self.levels.items():
|
||||
package, created = MapLevel.objects.update_or_create(name=name, defaults={
|
||||
'package': packages[level['package']],
|
||||
'altitude': level['altitude'],
|
||||
'name': level['name'],
|
||||
})
|
||||
if created:
|
||||
print('- Created level: '+name)
|
||||
|
||||
# Add new map sources
|
||||
print('- Updating sources…')
|
||||
for name, source in self.sources.items():
|
||||
source, created = MapSource.objects.update_or_create(name=name, defaults={
|
||||
'package': packages[source['package']],
|
||||
'image': File(open(source['filename'], 'rb')),
|
||||
'bottom': source['bounds'][0][0],
|
||||
'left': source['bounds'][0][1],
|
||||
'top': source['bounds'][1][0],
|
||||
'right': source['bounds'][1][1],
|
||||
})
|
||||
if created:
|
||||
print('- Created source: '+name)
|
||||
|
||||
# Remove old sources
|
||||
for source in MapSource.objects.exclude(name__in=self.sources.keys()):
|
||||
print('- Deleted source: '+source.name)
|
||||
source.delete()
|
||||
|
||||
# Remove old levels
|
||||
for level in MapLevel.objects.exclude(name__in=self.levels.keys()):
|
||||
print('- Deleted level: '+level.name)
|
||||
level.delete()
|
||||
|
||||
# Remove old packages
|
||||
for package in MapPackage.objects.exclude(name__in=self.packages.keys()):
|
||||
print('- Deleted package: '+package.name)
|
||||
package.delete()
|
|
@ -3,5 +3,6 @@ from django.conf.urls import url
|
|||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^sources/(?P<filename>[^/]+)$', views.source, name='map.source'),
|
||||
url(r'^sources/(?P<source>[^/]+)$', views.source, name='map.source'),
|
||||
url(r'^data/add$', views.source, name='map.edit.source'),
|
||||
]
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import mimetypes
|
||||
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import render
|
||||
|
||||
from ..mapdata import mapmanager
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from .models import MapSource
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def source(request, filename):
|
||||
source = mapmanager.sources_by_filename.get(filename)
|
||||
if source is None:
|
||||
raise Http404('Source does not exist')
|
||||
|
||||
response = HttpResponse(content_type=mimetypes.guess_type(source.src)[0])
|
||||
with open(source.src, 'rb') as f:
|
||||
response.write(f.read())
|
||||
def source(request, source):
|
||||
source = get_object_or_404(MapSource, name=source)
|
||||
response = HttpResponse(content_type=mimetypes.guess_type(source.image.name)[0])
|
||||
for chunk in source.image.chunks():
|
||||
response.write(chunk)
|
||||
return response
|
||||
|
|
|
@ -18,8 +18,6 @@ DATA_DIR = config.get('c3nav', 'datadir', fallback=os.environ.get('DATA_DIR', 'd
|
|||
LOG_DIR = os.path.join(DATA_DIR, 'logs')
|
||||
MEDIA_ROOT = os.path.join(DATA_DIR, 'media')
|
||||
|
||||
MAP_DIRS = tuple(n for n in config.get('c3nav', 'mapdirs', fallback='').split(',') if n)
|
||||
|
||||
if not os.path.exists(DATA_DIR):
|
||||
os.mkdir(DATA_DIR)
|
||||
if not os.path.exists(LOG_DIR):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue