diff --git a/src/c3nav/editor/static/editor/js/editor.js b/src/c3nav/editor/static/editor/js/editor.js index cd69262e..312ab026 100644 --- a/src/c3nav/editor/static/editor/js/editor.js +++ b/src/c3nav/editor/static/editor/js/editor.js @@ -185,6 +185,8 @@ editor = { // Clear snap indicators when unloading editor._clear_snap_indicators(); + + editor._destroy_staircase_editing(); }, _fill_level_control: function (level_control, level_list, geometryURLs) { var levels = level_list.find('a'); @@ -1365,7 +1367,7 @@ editor = { } else if (mapitem_type) { // creating a new geometry, already drawn but form was rejected options = editor._get_mapitem_type_style(mapitem_type); - if (mapitem_type === 'area') { + if (mapitem_type === 'area' || mapitem_type === 'staircase') { options.fillOpacity = 0.5; } } @@ -1385,7 +1387,7 @@ editor = { } else if (form.is('[data-new]')) { // create new geometry options = editor._get_mapitem_type_style(mapitem_type); - if (mapitem_type === 'area') { + if (mapitem_type === 'area' || mapitem_type === 'staircase') { options.fillOpacity = 0.5; } form.addClass('creation-lock'); @@ -1434,6 +1436,10 @@ editor = { } startGeomEditing(selected_geomtype); } + + if (mapitem_type === 'staircase') { + editor._setup_staircase_editing() + } } }, _cancel_editing: function () { @@ -1886,6 +1892,127 @@ editor = { if (editor._snap_indicator) { editor._snap_indicator.clearLayers(); } + }, + + _setup_staircase_editing: function() { + editor._staircase_steps_count = 10 + editor._staircase_layer = L.layerGroup().addTo(editor.map); + $('#stairway-steps').on('input', function() { + editor._staircase_steps_count = parseInt($(this).val()) || 10; + editor._update_staircase_preview(); + }); + + editor.map.on('editable:editing', editor._update_staircase_preview); + }, + + _destroy_staircase_editing: function() { + if (editor._staircase_layer !== null) { + editor.map.removeLayer(editor._staircase_layer) + editor._staircase_layer = null + } + editor.map.off('editable:editing', editor._update_staircase_preview) + if (editor._current_editing_shape !== null) { + editor._current_editing_shape.editor.cancelDrawing() + editor._current_editing_shape.remove() + editor._current_editing_shape = null + } + }, + + _transform_point_for_staircase: function(p, p0, cos_a, sin_a) { + return { + x: + (p.x - p0.x) * cos_a + (p.y - p0.y) * sin_a + p0.x, + y: - (p.x - p0.x) * sin_a + (p.y - p0.y) * cos_a + p0.y, + } + }, + + _transform_for_staircase: function(xs, ys, num_stairs) { + let base_length = Math.sqrt((xs[1]-xs[0])**2 + (ys[1]-ys[0])**2) + let cos_a = (xs[1] - xs[0]) / base_length + let sin_a = (ys[1] - ys[0]) / base_length + let p0 = { x: xs[0], y: ys[0] } + + xs = points.map(p => editor._transform_point_for_staircase(p, p0, cos_a, sin_a).x) + ys = points.map(p => editor._transform_point_for_staircase(p, p0, cos_a, sin_a).y) + n = xs.length + + if (Math.abs(Math.max(...ys) - ys[0]) > Math.abs(Math.min(...ys) - ys[0])) { + height = Math.max(...ys) - ys[0] + } else { + height = Math.min(...ys) - ys[0] + } + //console.log(xs, ys, base_length, height) + + lines = [{p1: { x: xs[0], y: ys[0] }, p2: { x: xs[1], y: ys[1] }}] + for (i = 1; i < num_stairs; ++i) { + // intersect line y=y0+height/num_stairs*i with all transformed (xs,ys) + y = ys[0] + height/num_stairs*i + inters_xs = [] + for (j = 0; j < n; ++j) { + y1 = ys[j] + y2 = ys[(j+1)%n] + x1 = xs[j] + x2 = xs[(j+1)%n] + if ((y1 > y && y2 > y) || (y1 < y && y2 < y)) { + //console.log("disconnected", y, j, x1,y1, x2,y2, xs, ys) + continue + } + + if (Math.abs(x2 - x1) < 0.0001) { + // vertical line, m would be infinity + inters_xs.push(x1) + continue + } + + m = (y2 - y1) / (x2 - x1) + q = y2 - m * x2 + //console.log("connected", y, j, x1,y1, x2,y2, m, q, xs, ys) + inters_xs.push((y - q) / m) + } + + //console.log("inters_xs", inters_xs) + if (inters_xs.length < 2) { + continue + } + + min_xs = Math.min(...inters_xs) + max_xs = Math.max(...inters_xs) + lines.push({p1: {x: min_xs-2, y: y}, p2: {x: max_xs+2, y: y}}) + } + + //console.log("untransformed lines", lines) + lines = lines.map(l => ({ + p1: editor._transform_point_for_staircase(l.p1, p0, cos_a, -sin_a), + p2: editor._transform_point_for_staircase(l.p2, p0, cos_a, -sin_a), + })) + + //console.log(lines) + return lines + }, + + _update_staircase_preview: function(e = null) { + if (editor._current_editing_shape === null) { + return + } + points = editor._current_editing_shape._parts[0] || [] + editor._staircase_layer.clearLayers() + //console.log(points) + if (points.length < 3) { + return + } + + xs = points.map(p => p.x) + ys = points.map(p => p.y) + lines = editor._transform_for_staircase(xs, ys, editor._staircase_steps_count) + + lines.forEach(l => { + L.polyline( + [ + editor.map.layerPointToLatLng([l.p1.x, l.p1.y]), + editor.map.layerPointToLatLng([l.p2.x, l.p2.y]), + ], + {color: "red"} + ).addTo(editor._staircase_layer); + }) } }; diff --git a/src/c3nav/editor/templates/editor/create_staircase.html b/src/c3nav/editor/templates/editor/create_staircase.html new file mode 100644 index 00000000..b17e0754 --- /dev/null +++ b/src/c3nav/editor/templates/editor/create_staircase.html @@ -0,0 +1,40 @@ +{% load bootstrap3 %} +{% load i18n %} + +{% include 'editor/fragment_levels.html' %} + +

+ {% blocktrans %}Add staircase{% endblocktrans %} +

+{% bootstrap_messages %} + +
+ {% csrf_token %} + {% bootstrap_form form %} +
+ + +
+ {% buttons %} + + + {% if can_edit %} + {% if not nosave %} + + {% endif %} + {% endif %} + + {% if can_edit %} + {% trans 'Cancel' %} + {% else %} + {% trans 'Back' %} + {% endif %} + + {% endbuttons %} +
diff --git a/src/c3nav/editor/templates/editor/list.html b/src/c3nav/editor/templates/editor/list.html index fc923038..1bd75d0a 100644 --- a/src/c3nav/editor/templates/editor/list.html +++ b/src/c3nav/editor/templates/editor/list.html @@ -20,6 +20,11 @@ {% blocktrans %}New {{ model_title }}{% endblocktrans %} + {% if model_title == "Stair" %} + + {% blocktrans %}New staircase{% endblocktrans %} + + {% endif %} {% endif %} {% if explicit_edit %} diff --git a/src/c3nav/editor/urls.py b/src/c3nav/editor/urls.py index fe4f50ef..4c987ea1 100644 --- a/src/c3nav/editor/urls.py +++ b/src/c3nav/editor/urls.py @@ -4,7 +4,7 @@ from django.views.generic import TemplateView from c3nav.editor.views.account import change_password_view, login_view, logout_view, register_view from c3nav.editor.views.changes import changeset_detail, changeset_edit, changeset_redirect -from c3nav.editor.views.edit import edit, graph_edit, level_detail, list_objects, main_index, sourceimage, space_detail +from c3nav.editor.views.edit import edit, graph_edit, level_detail, list_objects, main_index, staircase_edit, sourceimage, space_detail from c3nav.editor.views.overlays import overlays_list, overlay_features, overlay_feature_edit from c3nav.editor.views.quest import QuestFormView from c3nav.editor.views.users import user_detail, user_redirect @@ -33,7 +33,6 @@ def add_editor_urls(model_name, parent_model_name=None, with_list=True, explicit ]) return result - # todo: custom path converters urlpatterns = [ path('levels//', level_detail, name='editor.levels.detail'), @@ -91,3 +90,4 @@ urlpatterns.extend(add_editor_urls('LeaveDescription', 'Space')) urlpatterns.extend(add_editor_urls('CrossDescription', 'Space')) urlpatterns.extend(add_editor_urls('BeaconMeasurement', 'Space')) urlpatterns.extend(add_editor_urls('RangingBeacon', 'Space')) +urlpatterns.append(path('spaces//staircase', edit, name='editor.stairs.staircase', kwargs={'model': apps.get_model('mapdata', 'Stair')})) diff --git a/src/c3nav/editor/views/edit.py b/src/c3nav/editor/views/edit.py index fc3060c0..28442e52 100644 --- a/src/c3nav/editor/views/edit.py +++ b/src/c3nav/editor/views/edit.py @@ -70,6 +70,12 @@ def main_index(request): }) +@etag(editor_etag_func) +@accesses_mapdata +@sidebar_view +def staircase_edit(request, space): + return render(request, "editor/create_staircase.html") + @etag(editor_etag_func) @accesses_mapdata @sidebar_view @@ -405,7 +411,10 @@ def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, e "access_restriction_select": True, }) - return render(request, 'editor/edit.html', ctx) + if request.path.endswith("staircase"): + return render(request, 'editor/create_staircase.html', ctx) + else: + return render(request, 'editor/edit.html', ctx) def get_visible_spaces(request):