diff --git a/.circleci/config.yml b/.circleci/config.yml index 574f7a5f4..311943563 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ machine: jobs: django testing: docker: - - image: maxboh/docker-circleci-node-miniconda-gdal:graph_tool + - image: maxboh/docker-circleci-node-miniconda-gdal:graph_tool_stretch steps: - checkout - run: mkdir test-reports @@ -31,7 +31,7 @@ jobs: selenium testing: docker: - - image: maxboh/docker-circleci-node-miniconda-gdal:graph_tool + - image: maxboh/docker-circleci-node-miniconda-gdal:graph_tool_stretch steps: - checkout - run: @@ -74,7 +74,7 @@ jobs: setup database: docker: - - image: maxboh/docker-circleci-node-miniconda-gdal:graph_tool + - image: maxboh/docker-circleci-node-miniconda-gdal:graph_tool_stretch steps: - checkout - run: @@ -93,7 +93,7 @@ jobs: jasmine tests: docker: - - image: maxboh/docker-circleci-node-miniconda-gdal:graph_tool + - image: maxboh/docker-circleci-node-miniconda-gdal:graph_tool_stretch steps: - checkout - run: mkdir test-reports diff --git a/.gitignore b/.gitignore index ef6943658..04201ce37 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ ssh_pg_h2020.sh /package-lock.json /0.2.1&subdirectory /2.4.1 +.pytest_cache \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 08529e645..19165ca5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,8 @@ +<<<<<<< HEAD FROM circleci/python:3.6-stretch-node-browsers +======= +FROM maxboh/docker-circleci-node-miniconda-gdal:graph_tool_stretch +>>>>>>> master-upstream ENV CIRCLECIPATH $PATH diff --git a/VagrantProvisionUbuntu1804.sh b/VagrantProvisionUbuntu1804.sh index 7af839e0f..fe374da3c 100644 --- a/VagrantProvisionUbuntu1804.sh +++ b/VagrantProvisionUbuntu1804.sh @@ -24,8 +24,8 @@ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/source sudo apt-get update && sudo apt-get install -y --allow-unauthenticated yarn echo "Installing graph-tools..." -sudo add-apt-repository -y "deb http://downloads.skewed.de/apt/bionic bionic universe" sudo apt-key adv --keyserver pgp.skewed.de --recv-key 612DEFB798507F25 +sudo add-apt-repository -y "deb http://downloads.skewed.de/apt/bionic bionic universe" sudo apt-get update && sudo apt-get install -y --allow-unauthenticated python3-graph-tool sudo apt-get install -y libcairo2-dev libjpeg-dev libgif-dev diff --git a/repair/.gitignore b/repair/.gitignore index 9299f7301..9e02c9b69 100644 --- a/repair/.gitignore +++ b/repair/.gitignore @@ -4,3 +4,4 @@ static/bundles *.json !static/data/*.json /settings_dev_local_pg.py +/settings_tests_local_pg.py diff --git a/repair/apps/asmfa/factories.py b/repair/apps/asmfa/factories.py index 44e570136..4b6234a9b 100644 --- a/repair/apps/asmfa/factories.py +++ b/repair/apps/asmfa/factories.py @@ -63,8 +63,8 @@ class Meta: class ActivityFactory(NodeFactory): class Meta: model = models.Activity - name = factory.Sequence(lambda n: "Activity #%s" % n) - nace = '52.Retail' + name = factory.Sequence(lambda n: f'Activity #{n}') + nace = factory.Sequence(lambda n: f'E-{n}') activitygroup = factory.SubFactory(ActivityGroupFactory) @@ -210,10 +210,11 @@ class Meta: class FractionFlowFactory(FlowFactory): class Meta: model = models.FractionFlow + flow = factory.SubFactory(Actor2ActorFactory) + stock = factory.SubFactory(ActorStockFactory) origin = factory.SubFactory(ActorFactory) destination = factory.SubFactory(ActorFactory) material = factory.SubFactory(MaterialFactory) composition_name = factory.Sequence(lambda n: "Composition #%s" % n) nace = '52.Retail' amount = 0.0 - \ No newline at end of file diff --git a/repair/apps/asmfa/graphs/graph.py b/repair/apps/asmfa/graphs/graph.py index cbfb39508..52bee4941 100644 --- a/repair/apps/asmfa/graphs/graph.py +++ b/repair/apps/asmfa/graphs/graph.py @@ -1,12 +1,3 @@ -from repair.apps.asmfa.models import (Actor2Actor, FractionFlow, Actor, - ActorStock, Material, - StrategyFractionFlow) -from repair.apps.changes.models import (SolutionInStrategy, - ImplementationQuantity, - AffectedFlow, ActorInSolutionPart) -from repair.apps.statusquo.models import SpatialChoice -from repair.apps.utils.utils import descend_materials -from repair.apps.asmfa.graphs.graphwalker import GraphWalker, Formula try: import graph_tool as gt from graph_tool import stats as gt_stats @@ -14,17 +5,149 @@ import cairo except ModuleNotFoundError: pass -from django.db.models import Q, Sum + +from django.db.models import Q, Sum, F +from django.db.models.functions import Coalesce +from django.db import connection import numpy as np +import pandas as pd import datetime from io import StringIO from django.conf import settings import os from datetime import datetime from itertools import chain - +import itertools import time +from repair.apps.asmfa.models import (Actor2Actor, FractionFlow, Actor, + ActorStock, Material, + StrategyFractionFlow) +from repair.apps.utils.utils import get_annotated_fractionflows +from repair.apps.changes.models import (SolutionInStrategy, + ImplementationQuantity, + AffectedFlow, Scheme, + ImplementationArea) +from repair.apps.statusquo.models import SpatialChoice +from repair.apps.utils.utils import descend_materials, copy_django_model +from repair.apps.asmfa.graphs.graphwalker import GraphWalker + + +class Formula: + + def __init__(self, a=1, b=0, q=0): + ''' + linear change calculation + + absolute change: + v’ = v + delta + delta = a * q + b + + relative change: + v’ = v * factor + factor = a * q + b + + Parameters + ---------- + a: float, optional + by default 1 + q: float, optional + quantity (user input), by default 0 + b: float, optional + by default 0 + ''' + self.a = a + self.b = b + self.q = q + + @property + def is_absolute(self) -> bool: + """return True if is instance of AbsoluteFormula""" + return isinstance(self, AbsoluteFormula) + + @classmethod + def from_implementation(self, solution_part, implementation): + question = solution_part.question + is_absolute = solution_part.is_absolute + if is_absolute: + cls = AbsoluteFormula + else: + cls = RelativeFormula + if solution_part.scheme == Scheme.NEW and not is_absolute: + raise ValueError('new flows without reference can only be defined ' + 'as absolute changes') + a = solution_part.a + b = solution_part.b + q = 0 + + if question: + quantity = ImplementationQuantity.objects.get( + question=solution_part.question, + implementation=implementation) + # question overrides is_absolute of part + is_absolute = question.is_absolute + q = quantity.value + + formula = cls(a=a, b=b, q=q) + + return formula + +class AbsoluteFormula(Formula): + + def __init__(self, a=1, b=0, q=0): + super().__init__(a, b, q) + self.total = 0 + self.n_flows = 0 + + def set_total(self, flows): + self.total = sum(flows.values_list('strategy_amount', flat=True)) + self.n_flows = len(flows) + + def set_n_flows(self, n_flows: int): + self.n_flows = n_flows + + def calculate_delta(self, v: float=None): + ''' + Parameters + ---------- + v: float, + value to apply formula to + + Returns + ------- + delta: float + signed delta + ''' + if not self.n_flows: + return 0 + #raise ValueError('No Flows defined') + delta = self.a * self.q + self.b + total = getattr(self, 'total', 0) + if total and v is not None: + delta *= v / total + else: + delta /= self.n_flows + return delta + + +class RelativeFormula(Formula): + + def calculate_delta(self, v): + ''' + Parameters + ---------- + v: float, + value to apply formula to + + Returns + ------- + delta: float + signed delta + ''' + factor = self.a * self.q + self.b + delta = v * factor + return delta + class BaseGraph: def __init__(self, keyflow, tag=''): @@ -79,8 +202,8 @@ def build(self): Q(id__in=actorflows.values('origin_id')) | Q(id__in=actorflows.values('destination_id')) | Q(id__in=stockflows.values('origin_id')) - ) - flows = list(chain(actorflows, stockflows)) + ).values() + flows = list(chain(actorflows.values(), stockflows.values())) self.graph = gt.Graph(directed=True) @@ -92,13 +215,16 @@ def build(self): self.graph.new_vertex_property("string") self.graph.vertex_properties["name"] = \ self.graph.new_vertex_property("string") + self.graph.vertex_properties["downstream_balance_factor"] = \ + self.graph.new_vertex_property("double", val=1) actorids = {} for i in range(len(actors)): - self.graph.vp.id[i] = actors[i].id - self.graph.vp.bvdid[i] = actors[i].BvDid - self.graph.vp.name[i] = actors[i].name - actorids[actors[i].id] = i + actor_id = actors[i]['id'] + self.graph.vp.id[i] = actors[i]['id'] + self.graph.vp.bvdid[i] = actors[i]['BvDid'] + self.graph.vp.name[i] = actors[i]['name'] + actorids[actor_id] = i # Add the flows to the graph # need a persistent edge id, because graph-tool can reindex the edges @@ -109,35 +235,68 @@ def build(self): self.graph.new_edge_property("int") self.graph.edge_properties['process'] = \ self.graph.new_edge_property("int") + self.graph.edge_properties['waste'] = \ + self.graph.new_edge_property("bool") + self.graph.edge_properties['hazardous'] = \ + self.graph.new_edge_property("bool") for i in range(len(flows)): # get the start and and actor id's flow = flows[i] - v0 = actorids.get(flow.origin.id) - if not flow.to_stock: - v1 = actorids.get(flow.destination.id) + v0 = actorids.get(flow['origin_id']) + if not flow['to_stock']: + v1 = actorids.get(flow['destination_id']) else: v1 = v0 if (v0 != None and v1 != None): e = self.graph.add_edge( self.graph.vertex(v0), self.graph.vertex(v1)) - self.graph.ep.id[e] = flow.id - self.graph.ep.material[e] = flow.material_id - self.graph.ep.process[e] = \ - flow.process_id if flow.process_id is not None else - 1 - self.graph.ep.amount[e] = flow.amount + self.graph.ep.id[e] = flow['id'] + self.graph.ep.material[e] = flow['material_id'] + self.graph.ep.waste[e] = flow['waste'] + self.graph.ep.hazardous[e] = flow['hazardous'] + process_id = flow['process_id'] + self.graph.ep.process[e] = process_id\ + if process_id is not None else - 1 + self.graph.ep.amount[e] = flow['amount'] + + # get the original order of the vertices and edges in the graph + edge_index = np.fromiter(self.graph.edge_index, dtype=int) + vertex_index = np.fromiter(self.graph.vertex_index, dtype=int) + # get an array with the source and target ids of all edges + edges = self.graph.get_edges() + # get the amounts, sorted by the edge_index + amount = self.graph.ep.amount.a[edge_index] + + # put them in a Dataframe + df = pd.DataFrame(data={'amount': amount, + 'fromnode': edges[:, 0], + 'tonode': edges[: ,1],}) + + # sum up the in- and outflows for each node + sum_fromnode = df.groupby('fromnode').sum() + sum_fromnode.index.name = 'nodeid' + sum_fromnode.rename(columns={'amount': 'sum_outflows',}, inplace=True) + sum_tonode = df.groupby('tonode').sum() + sum_tonode.index.name = 'nodeid' + sum_tonode.rename(columns={'amount': 'sum_inflows',}, inplace=True) + + # merge in- and outflows + merged = sum_tonode.merge(sum_fromnode, how='outer', on='nodeid') + # calculate the balance_factor + merged['balance_factor'] = merged['sum_outflows'] / merged['sum_inflows'] + # set balance_factor to 1.0, if inflow or outflow is NAN + balance_factor = merged['balance_factor'].fillna(value=1).sort_index() + + # set balance_factor also to 1.0 if it is 0 or infinitive + balance_factor.loc[balance_factor==0] = 1 + balance_factor.loc[np.isinf(balance_factor)] = 1 + + # write the results to the property-map downstream_balance_factor + self.graph.vp.downstream_balance_factor.a[vertex_index] = balance_factor self.save() - # save graph image - #fn = "keyflow-{}-base.png".format(self.keyflow.id) - #fn = os.path.join(self.path, fn) - #pos = gt.draw.fruchterman_reingold_layout(self.graph, n_iter=1000) - #gt.draw.graph_draw(self.graph, pos, vertex_size=20, - # vertex_text=self.graph.vp.name, - # vprops={"text_position":0, - # "font_weight": cairo.FONT_WEIGHT_BOLD, "font_size":14}, - # output_size=(700,600), output=fn) return self.graph def validate(self): @@ -195,86 +354,132 @@ def load(self): self.graph = gt.load_graph(self.filename) return self.graph - def mock_changes(self): - '''make some random changes for testing''' - flows = FractionFlow.objects.filter( - keyflow=self.keyflow, destination__isnull=False) - flow_ids = np.array(flows.values_list('id', flat=True)) - # pick 30% of the flows for change of amount - choice = np.random.choice( - a=[False, True], size=len(flow_ids), p=[0.7, 0.3]) - picked_flows = flows.filter(id__in=flow_ids[choice]) - strat_flows = [] - for flow in picked_flows: - # vary between -25% and +25% - new_amount = flow.amount * (1 + (np.random.random() / 2 - 0.25)) - strat_flow = StrategyFractionFlow( - strategy=self.strategy, amount=new_amount, fractionflow=flow) - strat_flows.append(strat_flow) - StrategyFractionFlow.objects.bulk_create(strat_flows) - - # pick 5% of flows as base for new flows - choice = np.random.choice( - a=[False, True], size=len(flow_ids), p=[0.95, 0.05]) - picked_flows = flows.filter(id__in=flow_ids[choice]) - actors = Actor.objects.filter( - activity__activitygroup__keyflow=self.keyflow) - actor_ids = np.array(actors.values_list('id', flat=True)) - picked_actor_ids = np.random.choice(a=actor_ids, size=len(picked_flows)) - for i, flow in enumerate(picked_flows): - change_origin = np.random.randint(2) - new_target = actors.get(id=picked_actor_ids[i]) - # unset id - flow.pk = None - flow.strategy = self.strategy - flow.origin = flow.origin if not change_origin else new_target - flow.destination = flow.destination if change_origin else new_target - flow.amount += flow.amount * (np.random.random() / 2 - 0.25) - flow.save() + def _modify_flows(self, flows, formula: Formula, new_material=None, new_process=None, + new_waste=-1, new_hazardous=-1): + ''' + modify flows with formula + ''' + deltas = [] + if formula.is_absolute: + formula.set_total(flows) + if (new_material or new_process or + new_waste >= 0 or new_hazardous >= 0): + edges = self._get_edges(flows) + for i, flow in enumerate(flows): + delta = formula.calculate_delta(flow.strategy_amount) + if formula.is_absolute: + # cut the delta to avoid negative flow amounts + delta = max(-flow.strategy_amount, delta) + # if we have a relative change (*1.5), + # then the delta is +0.5*strategy_amount + # so we have to substract the original amount + else: + delta -= flow.strategy_amount + + deltas.append(delta) + if new_material: + self.graph.ep.material[edges[i]] = new_material.id + if new_process: + self.graph.ep.process[edges[i]] = new_process.id + if new_waste >= 0: + self.graph.ep.waste[edges[i]] = new_waste == 1 + if new_hazardous >= 0: + self.graph.ep.hazardous[edges[i]] = new_hazardous == 1 + return deltas + + def _create_flows(self, origins, destinations, material, process, formula): + ''' + create flows between all origin and destination actors + ''' + if not formula.is_absolute: + raise ValueError('Formula for CreateFlows must be absolute') + total = formula.calculate_delta() + flow_count = len(origins) * len(destinations) - def create_new_flows(self, implementation_flows, target_actor, - graph, formula, material=None, keep_origin=True): + new_flows = [] + if not flow_count: + print('WARNING: No orgins and/or destinations found ' + 'while creating new flows') + return new_flows, np.empty((0, )) + formula.set_n_flows(flow_count) + # equal distribution + amount_per_flow = formula.calculate_delta() + deltas = np.full((flow_count), amount_per_flow) + for origin, destination in itertools.product(origins, destinations): + new_flow = FractionFlow( + origin=origin, destination=destination, + material=material, process=process, + amount=0, + strategy=self.strategy, + keyflow=self.keyflow + ) + #new_flows.append(new_flow) + # spatialite doesn't set the ids when bulk creating + # when saving it does (weird) + new_flow.save() + o_vertex = self._get_vertex(origin.id) + d_vertex = self._get_vertex(destination.id) + new_edge = self.graph.add_edge(o_vertex, d_vertex) + self.graph.ep.id[new_edge] = new_flow.id + self.graph.ep.amount[new_edge] = 0 + self.graph.ep.material[new_edge] = new_flow.material.id + self.graph.ep.process[new_edge] = \ + new_flow.process.id if new_flow.process is not None else - 1 + new_flows.append(new_flow) + + #FractionFlow.objects.bulk_create(new_flows) + return new_flows, deltas + + def _shift_flows(self, referenced_flows, possible_new_targets, + formula, new_material=None, new_process=None, + shift_origin=True, reduce_reference=True, + new_waste=-1, new_hazardous=-1): ''' - creates new flows based on given implementation flows and redirects them + creates new flows based on given referenced flows and redirects them to target actor (either origin or destinations are changing) - implementation_flows stay untouched - graph is updated in place - Warning: side effects on implementation_flows + + referenced_flows are reduced by amout of new flows if reduce_reference + is True, otherwise they stay untouched + + returns flows to be changed in order of change and the deltas added to + be to each flow in walker algorithm in same order as flows ''' + changed_ref_flows = [] new_flows = [] + changed_ref_deltas = [] + new_deltas = [] - target_vertex = util.find_vertex(graph, graph.vp['id'], target_actor.id) - if(len(target_vertex) > 0): - target_vertex = target_vertex[0] - else: - target_vertex = graph.add_vertex() - graph.vp.id[target_vertex] = target_actor.id - graph.vp.bvdid[target_vertex] = target_actor.BvDid - graph.vp.name[target_vertex] = target_actor.name + # the actors to keep (not shifted) + ids = referenced_flows.values_list('destination') if shift_origin\ + else referenced_flows.values_list('origin') + actors_kept = Actor.objects.filter(id__in=ids) + # actors in possible new targets that are closest + closest_dict = self.find_closest_actor(actors_kept, + possible_new_targets) if formula.is_absolute: - total = implementation_flows.aggregate(total=Sum('amount'))['total'] - new_total = formula.calculate(total) + formula.set_total(referenced_flows) # create new flows and add corresponding edges - for flow in implementation_flows: - # ToDo: new flows keep the amount, they are reduced in the - # calculation ??? how does this affect the other flows? - # absolute change? - # flow.save() + for flow in referenced_flows: + kept_id = flow.destination_id if shift_origin \ + else flow.origin_id - # equally distribute amounts - amount = formula.calculate(flow.amount) / len(implementation_flows) + # no target actor found within range + if kept_id not in closest_dict: + continue - if formula.is_absolute: - # distribute total change to changes on edges - # depending on share of total - solution_factor = new_total * amount / total - else: - amount = formula.calculate(flow.amount) + # get new target out of dictionary + new_id = closest_dict[kept_id] + + new_vertex = self._get_vertex(new_id) - # Change flow in the graph - edges = util.find_edge(graph, graph.ep['id'], flow.id) + delta = formula.calculate_delta(flow.strategy_amount) + delta = min(delta, flow.strategy_amount) + + # the edge corresponding to the referenced flow + # (the one to be shifted) + edges = util.find_edge(self.graph, self.graph.ep['id'], flow.id) if len(edges) > 1: raise ValueError("FractionFlow.id ", flow.id, " is not unique in the graph") @@ -282,48 +487,228 @@ def create_new_flows(self, implementation_flows, target_actor, print("Cannot find FractionFlow.id ", flow.id, " in the graph") continue edge = edges[0] - if keep_origin: - new_edge = graph.add_edge(edge.source(), target_vertex) + + new_edge_args = [new_vertex, edge.target()] if shift_origin \ + else [edge.source(), new_vertex] + new_edge = self.graph.edge(*new_edge_args) + + # create a new fractionflow for the implementation flow in db, + # setting id to None creates new one when saving + # while keeping attributes of original model; + # the new flow is added with zero amount and to be changed + # by calculated delta + new_flow = copy_django_model(flow) + new_flow.id = None + new_flow.amount = 0 + if shift_origin: + new_flow.origin_id = new_id else: - new_edge = graph.add_edge(target_vertex, edge.target()) + new_flow.destination_id = new_id + if new_material: + new_flow.material = new_material + if new_process: + new_flow.process = new_process + if new_waste >= 0: + new_flow.waste = new_waste == 1 + if new_hazardous >= 0: + new_flow.hazardous = new_hazardous == 1 + + # strategy marks flow as new flow + new_flow.strategy = self.strategy + new_flow.save() + + # create the edge in the graph + new_edge = self.graph.add_edge(*new_edge_args) + self.graph.ep.id[new_edge] = new_flow.id + self.graph.ep.amount[new_edge] = 0 + + self.graph.ep.material[new_edge] = new_flow.material.id + # process doesn't have to be set, missing attributes + # are marked with -1 in graph (if i remember correctly?) + self.graph.ep.process[new_edge] = \ + new_flow.process.id if new_flow.process is not None else - 1 + self.graph.ep.waste[new_edge] = new_flow.waste + self.graph.ep.hazardous[new_edge] = new_flow.hazardous + + new_flows.append(new_flow) + new_deltas.append(delta) + + # reduce (resp. increase) the referenced flow by the same amount + if reduce_reference: + changed_ref_flows.append(flow) + changed_ref_deltas.append(-delta) - # create a new fractionflow for the implementation flow + # new flows shall be created before modifying the existing ones + return new_flows + changed_ref_flows, new_deltas + changed_ref_deltas + + + def _chain_flows(self, referenced_flows, possible_new_targets, + formula, new_material=None, new_process=None, + prepend=True, new_waste=-1, new_hazardous=-1): + ''' + creates new flows based on given referenced flows and prepends + (prepend==True) or appends (prepend==False) them + + if new flows already exist, changes existing ones instead + + returns new/changed flows and deltas in same order as flows + + ToDo: almost the same as shift_flows(), generalize! + ''' + if formula.is_absolute: + raise ValueError( + 'Formula for PrependFlow and AppendFlow must be relative') + + new_flows = [] + deltas = [] + + ids = referenced_flows.values_list('destination') if prepend\ + else referenced_flows.values_list('origin') + actors_kept = Actor.objects.filter(id__in=ids) + + closest_dict = self.find_closest_actor(actors_kept, + possible_new_targets) + + # create new flows and add corresponding edges + for flow in referenced_flows: + kept_id = flow.destination_id if prepend \ + else flow.origin_id + + # no target actor found within range + if kept_id not in closest_dict: + continue + + # get new target out of dictionary + new_id = closest_dict[kept_id] + + new_vertex = self._get_vertex(new_id) + + delta = formula.calculate_delta(flow.strategy_amount) + + # the edge corresponding to the referenced flow + edges = util.find_edge(self.graph, self.graph.ep['id'], flow.id) + if len(edges) > 1: + raise ValueError("FractionFlow.id ", flow.id, + " is not unique in the graph") + elif len(edges) == 0: + print("Cannot find FractionFlow.id ", flow.id, " in the graph") + continue + edge = edges[0] + + new_edge_args = [new_vertex, edge.source()] if prepend \ + else [edge.target(), new_vertex] + new_edge = self.graph.edge(*new_edge_args) + + # create a new fractionflow for the implementation flow in db, # setting id to None creates new one when saving - # while keeping attributes - flow.id = None - flow.amount = amount - if keep_origin: - flow.destination = target_actor + # while keeping attributes of original model; + # the new flow is added with zero amount and to be changed + # by calculated delta + new_flow = copy_django_model(flow) + new_flow.id = None + new_flow.amount = 0 + if prepend: + new_flow.destination_id = new_flow.origin_id + new_flow.origin_id = new_id else: - flow.origin = target_actor + new_flow.origin_id = new_flow.destination_id + new_flow.destination_id = new_id + if new_material: + new_flow.material = new_material + if new_process: + new_flow.process = new_process + if new_waste >= 0: + new_flow.waste = new_waste == 1 + if new_hazardous >= 0: + new_flow.hazardous = new_hazardous == 1 + # strategy marks flow as new flow - flow.strategy = self.strategy - flow.save() - new_flows.append(flow) + new_flow.strategy = self.strategy + new_flow.save() - graph.ep.id[new_edge] = flow.id - graph.ep.amount[new_edge] = amount - # ToDo: swap material - graph.ep.material[new_edge] = flow.material.id - graph.ep.process[new_edge] = \ - flow.process.id if flow.process is not None else - 1 + # create the edge in the graph + new_edge = self.graph.add_edge(*new_edge_args) + self.graph.ep.id[new_edge] = new_flow.id + self.graph.ep.amount[new_edge] = 0 - # return new_flows + self.graph.ep.material[new_edge] = new_flow.material.id + # process doesn't have to be set, missing attributes + # are marked with -1 in graph (if i remember correctly?) + self.graph.ep.process[new_edge] = \ + new_flow.process.id if new_flow.process is not None else - 1 + self.graph.ep.waste[new_edge] = new_flow.waste + self.graph.ep.hazardous[new_edge] = new_flow.hazardous - # return empty list for now - return [] + new_flows.append(new_flow) + deltas.append(delta) - def clean(self): + return new_flows, deltas + + def clean_db(self): ''' wipe all related StrategyFractionFlows - and related new FractionFlows + and related new FractionFlows from database ''' - flows = FractionFlow.objects.filter(strategy=self.strategy) - flows.delete() + new_flows = FractionFlow.objects.filter(strategy=self.strategy) + new_flows.delete() modified = StrategyFractionFlow.objects.filter(strategy=self.strategy) modified.delete() - def _get_flows(self, solution_part, implementation): + def _get_actors(self, flow_reference, implementation): + origins = destinations = [] + if flow_reference.origin_activity: + origins = Actor.objects.filter( + activity=flow_reference.origin_activity) + # filter by origin area + if flow_reference.origin_area: + # implementation area + implementation_area = flow_reference.origin_area\ + .implementationarea_set.get(implementation=implementation) + # if user didn't draw sth. take poss. impl. area instead + geom = implementation_area.geom or flow_reference.origin_area.geom + origins = origins.filter( + administrative_location__geom__intersects=geom) + if flow_reference.destination_activity: + destinations = Actor.objects.filter( + activity=flow_reference.destination_activity) + # filter by destination area + if flow_reference.destination_area: + implementation_area = flow_reference.destination_area\ + .implementationarea_set.get(implementation=implementation) + geom = implementation_area.geom or \ + flow_reference.destination_area.geom + destinations = destinations.filter( + administrative_location__geom__intersects=geom) + return origins, destinations + + def _get_referenced_flows(self, flow_reference, implementation): + ''' + return flows on actor level filtered by flow_reference attributes + and implementation areas + ''' + origins, destinations = self._get_actors(flow_reference, implementation) + flows = get_annotated_fractionflows(self.strategy.keyflow.id, + self.strategy.id) + flows = flows.filter( + origin__in=origins, + destination__in=destinations + ) + if flow_reference.include_child_materials: + impl_materials = Material.objects.filter(id__in=descend_materials( + [flow_reference.material])) + kwargs = { + 'strategy_material__in': impl_materials + } + else: + kwargs = { + 'strategy_material': flow_reference.material.id + } + if flow_reference.process: + kwargs['strategy_process'] = flow_reference.process.id + reference_flows = flows.filter(**kwargs) + return reference_flows + + def _get_affected_flows(self, solution_part): ''' filters flows by definitions in solution part return tuple (implementation flows, affected flows) @@ -331,57 +716,26 @@ def _get_flows(self, solution_part, implementation): # set the AffectedFlow include property to true affectedflows = AffectedFlow.objects.filter( solution_part=solution_part) - solution = solution_part.solution - # if no area is drawn by user take possible area - implementation_area = ( - implementation.geom or - solution.possible_implementation_area - ) # get FractionFlows related to AffectedFlow - affected_flows = FractionFlow.objects.none() + aff_flows = FractionFlow.objects.none() for af in affectedflows: - materials = descend_materials([af.material]) - affected_flows = \ - affected_flows | FractionFlow.objects.filter( - origin__activity=af.origin_activity, - destination__activity=af.destination_activity, - material__in=materials) - impl_materials = descend_materials( - [solution_part.implementation_flow_material]) - # there might be no implementation area defined for the solution - if not implementation_area: - # implementation flows - implementation_flows = FractionFlow.objects.filter( - origin__activity= - solution_part.implementation_flow_origin_activity, - destination__activity= - solution_part.implementation_flow_destination_activity, - material__in=impl_materials - ) - else: - origins = Actor.objects.filter( - activity=solution_part.implementation_flow_origin_activity) - destinations = Actor.objects.filter( - activity=solution_part.implementation_flow_destination_activity) - spatial_choice = solution_part.implementation_flow_spatial_application - # iterate collection - for geom in implementation_area: - if spatial_choice in [SpatialChoice.BOTH, SpatialChoice.ORIGIN]: - origins = origins.filter( - administrative_location__geom__intersects=geom) - if spatial_choice in [SpatialChoice.BOTH, - SpatialChoice.DESTINATION]: - destinations = destinations.filter( - administrative_location__geom__intersects=geom) - implementation_flows = FractionFlow.objects.filter( - origin__in=origins, - destination__in=destinations, - material__in=impl_materials + #materials = descend_materials([af.material]) + flows = get_annotated_fractionflows(self.strategy.keyflow.id, + self.strategy.id) + flows = flows.filter( + origin__activity = af.origin_activity, + destination__activity = af.destination_activity ) - return implementation_flows, affected_flows + kwargs = { + 'strategy_material': af.material.id + } + if af.process: + kwargs['strategy_process': af.process] + aff_flows = aff_flows | flows.filter(**kwargs) + return aff_flows - def _include(self, graph, flows, do_include=True): + def _include(self, flows, do_include=True): ''' include flows in graph, excludes all others set do_include=False to exclude @@ -389,111 +743,182 @@ def _include(self, graph, flows, do_include=True): ''' start = time.time() # include affected edges - edges = self._get_edges(graph, flows) + edges = self._get_edges(flows) for edge in edges: - graph.ep.include[edge] = do_include + self.graph.ep.include[edge] = do_include end = time.time() - def _reset_include(self, graph, do_include=True): + def _reset_include(self, do_include=True): # exclude all - graph.ep.include.a[:] = do_include + self.graph.ep.include.a[:] = do_include - def _get_edges(self, graph, flows): + def _get_edges(self, flows): edges = [] for flow in flows: - e = util.find_edge(graph, graph.ep['id'], flow.id) + e = util.find_edge(self.graph, self.graph.ep['id'], flow.id) if len(e) > 0: edges.append(e[0]) else: # shouldn't happen if graph is up to date - print('Warning: graph is missing flows') + raise Exception(f'graph is missing flow {flow.id}') return edges - def _build_formula(self, solution_part, implementation): - question = solution_part.question - is_absolute = solution_part.is_absolute - a = solution_part.a - b = solution_part.b - q = 0 - - if question: - quantity = ImplementationQuantity.objects.get( - question=solution_part.question, - implementation=implementation) - # question overrides is_absolute of part - is_absolute = question.is_absolute - q = quantity.value - - formula = Formula(a=a, b=b, q=q, is_absolute=is_absolute) - - return formula + def _get_vertex(self, id): + ''' return vertex with given id, creates vertex with corresponding + actor information if id is not in graph yet''' + vertices = util.find_vertex( + self.graph, self.graph.vp['id'], id) + + if(len(vertices) > 0): + return vertices[0] + + # add actor to graph + actor = Actor.objects.get(id=id) + vertex = self.graph.add_vertex() + # not existing in basegraph -> no flows in or out in status quo -> + # balance factor of 1 + self.graph.vp.downstream_balance_factor[vertex] = 1 + self.graph.vp.id[vertex] = id + self.graph.vp.bvdid[vertex] = actor.BvDid + self.graph.vp.name[vertex] = actor.name + return vertex def build(self): - base_graph = BaseGraph(self.keyflow, tag=self.tag) + # if the base graph is not built yet, it shouldn't be done automatically + # there are permissions controlling who is allowed to build it and + # who isn't if not base_graph.exists: raise FileNotFoundError - g = base_graph.load() - gw = GraphWalker(g) - - # remove previous calc. from database - self.clean() - - # add change attribute, it defaults to 0.0 - g.ep.change = g.new_edge_property("float") - # add include attribute, it defaults to False - g.ep.include = g.new_edge_property("bool") - g.ep.changed = g.new_edge_property("bool") - - # get the solutions in this strategy and order them by priority - solutions_in_strategy = SolutionInStrategy.objects.filter( - strategy=self.strategy).order_by('priority') - for solution_in_strategy in solutions_in_strategy.order_by('priority'): - solution = solution_in_strategy.solution + self.graph = base_graph.load() + gw = GraphWalker(self.graph) + self.clean_db() + #self.mock_changes() + #return + + # attribute marks edges to be ignored or not (defaults to False) + self.graph.ep.include = self.graph.new_edge_property("bool") + # attribute marks changed edges (defaults to False) + self.graph.ep.changed = self.graph.new_edge_property("bool") + + # get the implementations of the solution in this strategy + # and order them by priority + # wording might confuse (implementation instead of solution in strategy) + # but we shifted to using the term "implementation" in most parts + implementations = SolutionInStrategy.objects.filter( + strategy=self.strategy).order_by('priority') + for implementation in implementations.order_by('priority'): + solution = implementation.solution parts = solution.solution_parts.all() # get the solution parts using the reverse relation for solution_part in parts.order_by('priority'): - formula = self._build_formula( - solution_part, solution_in_strategy) - - implementation_flows, affected_flows = \ - self._get_flows(solution_part, solution_in_strategy) - - if solution_part.implements_new_flow: - target_activity = solution_part.new_target_activity - keep_origin = solution_part.keep_origin - picked = ActorInSolutionPart.objects.filter( - solutionpart=solution_part, - implementation=solution_in_strategy) - # no calculation possible with no actor picked by user - if len(picked) == 0: - continue - target_actor = picked[0].actor - # the new flows will be the ones affected by - # calculation first - implementation_flows = self.create_new_flows( - implementation_flows, target_actor, - g, formula, keep_origin=keep_origin) - + deltas = [] + formula = Formula.from_implementation( + solution_part, implementation) + + # all but new flows reference existing flows (there the + # implementation flows are the new ones themselves) + reference = solution_part.flow_reference + changes = solution_part.flow_changes + if solution_part.scheme != Scheme.NEW: + implementation_flows = self._get_referenced_flows( + reference, implementation) + + if solution_part.scheme == Scheme.MODIFICATION: + kwargs = {} + if changes: + kwargs['new_material'] = changes.material + kwargs['new_process'] = changes.process + kwargs['new_waste'] = changes.waste + kwargs['new_hazardous'] = changes.hazardous + + deltas = self._modify_flows(implementation_flows, formula, + **kwargs) + + elif solution_part.scheme == Scheme.SHIFTDESTINATION: + o, possible_destinations = self._get_actors( + changes, implementation) + implementation_flows, deltas = self._shift_flows( + implementation_flows, possible_destinations, + formula, shift_origin=False, + new_material=changes.material, + new_process=changes.process, + new_waste=changes.waste, + new_hazardous=changes.hazardous + ) + + elif solution_part.scheme == Scheme.SHIFTORIGIN: + possible_origins, d = self._get_actors( + changes, implementation) + implementation_flows, deltas = self._shift_flows( + implementation_flows, possible_origins, + formula, shift_origin=True, + new_material=changes.material, + new_process=changes.process, + new_waste=changes.waste, + new_hazardous=changes.hazardous + ) + + elif solution_part.scheme == Scheme.NEW: + origins, destinations = self._get_actors( + changes, implementation) + implementation_flows, deltas = self._create_flows( + origins, destinations, changes.material, + changes.process, formula) + + elif solution_part.scheme == Scheme.PREPEND: + possible_origins, d = self._get_actors( + changes, implementation) + if len(possible_origins) > 0: + implementation_flows, deltas = self._chain_flows( + implementation_flows, possible_origins, + formula, prepend=True, + new_material=changes.material, + new_process=changes.process, + new_waste=changes.waste, + new_hazardous=changes.hazardous) + else: + print('Warning: no new targets found! Skipping prepend') + + elif solution_part.scheme == Scheme.APPEND: + o, possible_destinations = self._get_actors( + changes, implementation) + if len(possible_destinations) > 0: + implementation_flows, deltas = self._chain_flows( + implementation_flows, possible_destinations, + formula, prepend=False, + new_material=changes.material, + new_process=changes.process, + new_waste=changes.waste, + new_hazardous=changes.hazardous) + else: + print('Warning: no new targets found! Skipping append') + + else: + raise ValueError( + f'scheme {solution_part.scheme} is not implemented') + + affected_flows = self._get_affected_flows(solution_part) # exclude all edges - self._reset_include(g, do_include=False) + self._reset_include(do_include=False) # include affected flows - self._include(g, affected_flows) - # exclude implementation flows (ToDo: side effects?) - self._include(g, implementation_flows, do_include=False) + self._include(affected_flows) + # exclude implementation flows in case they are also in affected + # flows (ToDo: side effects?) + #self._include(implementation_flows, do_include=False) - impl_edges = self._get_edges(g, implementation_flows) + impl_edges = self._get_edges(implementation_flows) - # ToDo: SolutionInStrategy geometry for filtering? - gw = GraphWalker(g) - g = gw.calculate(impl_edges, formula) + gw = GraphWalker(self.graph) + self.graph = gw.calculate(impl_edges, deltas) + + # save modifications and new flows into database + self.translate_to_db() + self.graph.ep.changed.a[:] = False - self.graph = g # save the strategy graph to a file self.graph.save(self.filename) - # save modifications and new flows into database - self.translate_to_db() return self.graph def translate_to_db(self): @@ -506,31 +931,224 @@ def translate_to_db(self): new_amount = self.graph.ep.amount[edge] # get the related FractionFlow flow = FractionFlow.objects.get(id=self.graph.ep.id[edge]) + material = self.graph.ep.material[edge] + process = self.graph.ep.process[edge] + if process == -1: + process = None + waste = self.graph.ep.waste[edge] + hazardous = self.graph.ep.hazardous[edge] # new flow is marked with strategy relation # (no seperate strategy fraction flow needed) if flow.strategy is not None: flow.amount = new_amount + flow.hazardous = hazardous + flow.material_id = material + flow.waste = waste + flow.process_id = process flow.save() # changed flow gets a related strategy fraction flow holding changes else: - # ToDo: get material from graph - strat_flow = StrategyFractionFlow( - strategy=self.strategy, amount=new_amount, - fractionflow=flow, - material_id=flow.material) - strat_flows.append(strat_flow) + ex = StrategyFractionFlow.objects.filter( + fractionflow=flow, strategy=self.strategy) + # if there already was a modification, overwrite it + if len(ex) == 1: + strat_flow = ex[0] + strat_flow.amount = new_amount + strat_flow.material_id = material + strat_flow.waste = waste + strat_flow.hazardous = hazardous + strat_flow.process_id = process + strat_flow.save() + elif len(ex) > 1: + raise Exception('more than StrategyFractionFlow ' + 'found per flow. This should not happen.') + else: + strat_flow = StrategyFractionFlow( + strategy=self.strategy, + amount=new_amount, + fractionflow=flow, + material_id=material, + waste=waste, + hazardous=hazardous, + process_id=process + ) + strat_flows.append(strat_flow) StrategyFractionFlow.objects.bulk_create(strat_flows) - def to_queryset(self): - if not self.graph: - self.load() + @staticmethod + def find_closest_actor(actors_in_solution, + possible_target_actors, + max_distance: int=500, + absolute_max_distance: int=100000): + # ToDo: for each actor pick a closest new one + # don't distribute amounts equally! + # (calc. amount based on the shifted flow for relative or distribute + # absolute total based on total amounts going into the shifted + # actor before?) + backend = connection.vendor + st_dwithin = {'sqlite': 'PtDistWithin', + 'postgresql': 'st_dwithin',} + + cast_to_geography = {'sqlite': '', + 'postgresql': '::geography',} + + # code for auto picking actor by distance + # start with maximum distance of 500m + target_actors = dict() + + actors_not_found_yet = actors_in_solution + + while (actors_not_found_yet + and max_distance < absolute_max_distance): + + query_actors_in_solution = actors_not_found_yet \ + .annotate(pnt=F('administrative_location__geom')) \ + .values('id', 'pnt') \ + .query + + query_target_actors = possible_target_actors \ + .annotate(pnt=F('administrative_location__geom')) \ + .values('id', 'pnt') \ + .query + + if backend == 'sqlite': + querytext_actors_in_solution, params_actors_in_solution = \ + query_actors_in_solution.sql_with_params() + querytext_actors_in_solution = \ + querytext_actors_in_solution.replace( + 'CAST (AsEWKB("asmfa_administrativelocation"."geom") AS BLOB)', + '"asmfa_administrativelocation"."geom"') + + querytext_target_actors, params_target_actors = \ + query_target_actors.sql_with_params() + querytext_target_actors = querytext_target_actors.replace( + 'CAST (AsEWKB("asmfa_administrativelocation"."geom") AS BLOB)', + '"asmfa_administrativelocation"."geom"') + + query = f''' + WITH + ais AS ({querytext_actors_in_solution}), + pta AS ({querytext_target_actors}) + SELECT + a.actor_id, + a.target_actor_id + FROM + (SELECT + ais.id AS actor_id, + pta.id AS target_actor_id, + st_distance( + ST_Transform(ais.pnt, 3035), + ST_Transform(pta.pnt, 3035)) AS meter + FROM ais, pta + WHERE PtDistWithin(ais.pnt, + pta.pnt, + {max_distance}) + ) a, + (SELECT + ais.id AS actor_id, + min(st_distance( + ST_Transform(ais.pnt, 3035), + ST_Transform(pta.pnt, 3035))) AS min_meter + FROM ais, pta + WHERE PtDistWithin(ais.pnt, + pta.pnt, + {max_distance}) + GROUP BY ais.id + ) b + WHERE a.actor_id = b.actor_id + AND a.meter = b.min_meter + ''' + + params = params_actors_in_solution + params_target_actors + + elif backend == 'postgresql': + query_actors_in_solution = str( + query_actors_in_solution).replace( + '"asmfa_administrativelocation"."geom"::bytea', + '"asmfa_administrativelocation"."geom"') + + query_target_actors = str(query_target_actors).replace( + '"asmfa_administrativelocation"."geom"::bytea', + '"asmfa_administrativelocation"."geom"') + + query = f''' + WITH + ais AS ({query_actors_in_solution}), + pta AS ({query_target_actors}) + SELECT + a.actor_id, + a.target_actor_id + FROM + (SELECT + ais.id AS actor_id, + pta.id AS target_actor_id, + row_number() OVER( + PARTITION BY ais.id + ORDER BY st_distance( + ST_Transform(ais.pnt, 3035), ST_Transform(pta.pnt, 3035)) + ) AS rn + FROM ais, pta + WHERE st_dwithin(ais.pnt::geography, + pta.pnt::geography, + {max_distance}) + ) a + WHERE a.rn = 1 + ''' + + params = None - for e in self.graph.edges(): - flow = {} - flow['id'] = self.graph.ep.id[e] - flow['source'] = self.graph.vp.id[e.source()] - flow['target'] = self.graph.vp.id[e.target()] - flow['material'] = self.graph.ep.material[e] - flow['amount'] = self.graph.ep.amount[e] - flows.append(flow) \ No newline at end of file + else: + raise ConnectionError(f'unknown backend: {backend}') + + + with connection.cursor() as cursor: + cursor.execute(query, params) + rows = cursor.fetchall() + + target_actors.update(dict(rows)) + + actors_not_found_yet = actors_in_solution.exclude( + id__in=target_actors.keys()) + + max_distance *= 2 + max_distance = min(max_distance, absolute_max_distance) + + return target_actors + + def mock_changes(self): + '''make some random changes for testing''' + flows = FractionFlow.objects.filter( + keyflow=self.keyflow, destination__isnull=False) + flow_ids = np.array(flows.values_list('id', flat=True)) + # pick 30% of the flows for change of amount + choice = np.random.choice( + a=[False, True], size=len(flow_ids), p=[0.7, 0.3]) + picked_flows = flows.filter(id__in=flow_ids[choice]) + strat_flows = [] + for flow in picked_flows: + # vary between -25% and +25% + new_amount = flow.amount * (1 + (np.random.random() / 2 - 0.25)) + strat_flow = StrategyFractionFlow( + strategy=self.strategy, amount=new_amount, fractionflow=flow) + strat_flows.append(strat_flow) + StrategyFractionFlow.objects.bulk_create(strat_flows) + + # pick 5% of flows as base for new flows + choice = np.random.choice( + a=[False, True], size=len(flow_ids), p=[0.95, 0.05]) + picked_flows = flows.filter(id__in=flow_ids[choice]) + actors = Actor.objects.filter( + activity__activitygroup__keyflow=self.keyflow) + actor_ids = np.array(actors.values_list('id', flat=True)) + picked_actor_ids = np.random.choice(a=actor_ids, size=len(picked_flows)) + for i, flow in enumerate(picked_flows): + change_origin = np.random.randint(2) + new_target = actors.get(id=picked_actor_ids[i]) + # unset id + flow.pk = None + flow.strategy = self.strategy + flow.origin = flow.origin if not change_origin else new_target + flow.destination = flow.destination if change_origin else new_target + flow.amount += flow.amount * (np.random.random() / 2 - 0.25) + flow.save() \ No newline at end of file diff --git a/repair/apps/asmfa/graphs/graphwalker.py b/repair/apps/asmfa/graphs/graphwalker.py index f93f529ed..261f9b4cf 100644 --- a/repair/apps/asmfa/graphs/graphwalker.py +++ b/repair/apps/asmfa/graphs/graphwalker.py @@ -1,148 +1,89 @@ try: import graph_tool as gt - from graph_tool import util, search + from graph_tool import search from graph_tool.search import BFSVisitor except ModuleNotFoundError: class BFSVisitor: pass import copy -import numpy as np -from django.db.models import Q -import time - -from repair.apps.asmfa.models.flows import FractionFlow -from repair.apps.asmfa.models import Actor - - -class Formula: - def __init__(self, a=1, b=0, q=0, is_absolute=False): - ''' - linear change calculation - - absolute change: - v’ = v + delta - delta = a * q + b - - relative change: - v’ = v * factor - factor = a * q + b - - Parameters - ---------- - a: float, optional - by default 1 - q: float, optional - quantity (user input), by default 0 - b: float, optional - by default 0 - is_absolute: bool, optional - absolute change, by default False - ''' - self.a = a - self.b = b - self.q = q - self.is_absolute = is_absolute - - def calculate(self, v): - ''' - Parameters - ---------- - v: float, - value to apply formula to - - Returns - ------- - v': float - calculated value - ''' - return self.calculate_factor(v) * v - - def calculate_factor(self, v=None): - ''' - Parameters - ---------- - v: float, optional - needed for calculation of a - - Returns - ------- - factor: float - calculated factor - ''' - if self.is_absolute: - if v is None: - raise ValueError('Value needed for calculation of a factor for ' - 'absolute changes') - delta = self.a * self.q + self.b - v_ = v + delta - factor = v_ / v - else: - factor = self.a * self.q + self.b - return factor - class NodeVisitor(BFSVisitor): - def __init__(self, name, solution, amount, visited, change): + def __init__(self, name, amount, change, + balance_factor, forward=True): self.id = name - self.solution = solution self.amount = amount - self.visited = visited self.change = change + self.balance_factor = balance_factor + self.forward = forward + + def examine_vertex(self, u): + vertex_id = int(u) + out_degree = u.out_degree() + if not out_degree: + return + + bf = self.balance_factor[vertex_id] + sum_in_deltas = u.in_degree(weight=self.change) + balanced_delta = sum_in_deltas * bf + sum_out_f = u.out_degree(weight=self.amount) + if sum_out_f: + amount_factor = balanced_delta / sum_out_f + else: + amount_factor = balanced_delta / out_degree + + for e_out in u.out_edges(): + amount_delta = self.amount[e_out] * amount_factor + if self.forward: + self.change[e_out] += amount_delta + else: + if abs(self.change[e_out]) < abs(amount_delta): + self.change[e_out] = amount_delta + else: + self.change[e_out] += amount_delta - def discover_vertex(self, u): - """This is invoked when a vertex is encountered for the first time.""" - pass + +class NodeVisitorBalanceDeltas(BFSVisitor): + + def __init__(self, name, amount, change, + balance_factor): + self.id = name + self.amount = amount + self.change = change + self.balance_factor = balance_factor def examine_vertex(self, u): - """Compute the amount change on each inflow for the vertex - - This function is invoked on a vertex as it is popped from the queue. - - Returns - ------- - dict - {edge : change} - """ - changes = {} - if u.in_degree() > 0: - all_in = list(u.in_edges()) - # ToDo: np e.g. g.get_out_edges(node, eprops=[g.ep.amount]) - for i, e in enumerate(all_in): - if not self.visited[e]: - e_src = e.source() - e_src_out = [e for e in e_src.out_edges()] - if len(e_src_out) > 1: - # For the case when an inflow edge shares the - # source vertex - self.change[e] = ( - self.amount[e] / sum( - [self.amount[out_f] for out_f in e_src_out]) - ) * self.solution - else: - self.change[e] = ( - self.amount[e] / sum( - [self.amount[in_f] for in_f in all_in]) - ) * self.solution - # print(self.id[e.source()], '-->', - # self.id[e.target()], self.change[e]) - self.visited[e] = True + vertex_id = int(u) + out_degree = u.out_degree() + if not out_degree: + return + + sum_in_deltas = u.in_degree(self.change) + sum_out_deltas = u.out_degree(self.change) + bf = self.balance_factor[vertex_id] + balanced_out_deltas = sum_out_deltas / bf + balanced_delta = sum_in_deltas - balanced_out_deltas + if abs(balanced_delta) < 0.0000001: + return + sum_out_f = u.out_degree(weight=self.amount) + if sum_out_f: + amount_factor = balanced_delta / sum_out_f else: - # print("source node,", u.vp.id) - pass + amount_factor = balanced_delta / out_degree + for e_out in u.out_edges(): + amount_delta = self.amount[e_out] * amount_factor + self.change[e_out] += amount_delta -def traverse_graph(g, edge, solution, amount, upstream=True): +def traverse_graph(g, edge, delta, upstream=True): """Traverse the graph in a breadth-first-search manner Parameters ---------- g : the graph to explore edge : the starting edge, normally this is the *solution edge* - solution : relative change of implementation flow - (factor to multiply amount with) - amount : PropertyMap + delta : signed change in absolute value (eg. tons) on the implementation flow (delta). For example -26.0 (tons) upstream : The direction of traversal. When upstream is True, the graph is explored upstream first, otherwise downstream first. @@ -151,84 +92,212 @@ def traverse_graph(g, edge, solution, amount, upstream=True): Edge ProperyMap (float) The signed change on the edges """ - # Property map for keeping track of the visited edge. Once an edge has - # been visited it won't be processed anymore. - r = (False for x in g.get_edges()) - visited = g.new_edge_property("bool", vals=r) + plot = False + + amount = g.ep.amount change = g.new_edge_property("float", val=0.0) - # G.edge_properties["change"] = change + total_change = g.new_edge_property("float", val=0.0) + + if plot: + # prepare plotting of intermediate results + from repair.apps.asmfa.tests import flowmodeltestdata + flowmodeltestdata.plot_materials(g, file='materials.png') + flowmodeltestdata.plot_amounts(g,'amounts.png', 'amount') + g.ep.change = change + + # We are only interested in the edges that define the solution + g.set_edge_filter(g.ep.include) + MAX_ITERATIONS = 20 + balance_factor = g.vp.downstream_balance_factor.a + + # make a first run with the given changes to the implementation edge + # By default we go upstream first, because 'demand dictates supply' if upstream: + node = edge.source() g.set_reversed(True) + balance_factor = 1 / balance_factor else: + node = edge.target() g.set_reversed(False) - node = edge.target() - # We are only interested in the edges that define the solution - g.set_edge_filter(g.ep.include) - # print("\nTraversing in 1. direction") - search.bfs_search(g, node, NodeVisitor( - g.vp["id"], solution, amount, visited, change)) - if g.is_reversed(): - g.set_reversed(False) + + # initialize the node-visitors + node_visitor = NodeVisitor(g.vp["id"], amount, change, + balance_factor) + node_visitor2 = NodeVisitorBalanceDeltas(g.vp["id"], amount, change, + balance_factor) + + node_visitor.forward = True + total_change.a[:] = 0 + new_delta = delta + i = 0 + change[edge] = new_delta + # start in one direction + search.bfs_search(g, node, node_visitor) + change[edge] = new_delta + + if plot: + ## Plot changes after forward run + g.ep.change.a[:] = change.a + flowmodeltestdata.plot_amounts(g,'plastic_deltas.png', 'change') + + node = reverse_graph(g, node_visitor, node_visitor2, edge) + search.bfs_search(g, node, node_visitor) + change[edge] = new_delta + + if plot: + ## Plot changes after backward run + g.ep.change.a[:] = change.a + flowmodeltestdata.plot_amounts(g,'plastic_deltas.png', 'change') + + # balance out the changes + search.bfs_search(g, node, node_visitor2) + change[edge] = new_delta + + # add up the total changes + total_change.a += change.a + + if plot: + ## Plot total changes + g.ep.change.a[:] = total_change.a + flowmodeltestdata.plot_amounts(g,f'plastic_deltas_{i}.png', 'change') + + node = reverse_graph(g, node_visitor, node_visitor2, edge) + + if upstream: + if node.in_degree(): + sum_f = node.in_degree(weight=total_change) + new_delta = delta - sum_f + else: + new_delta = 0 else: - g.set_reversed(True) - # print("\nTraversing in 2. direction") - search.bfs_search(g, node, NodeVisitor( - g.vp["id"], solution, amount, visited, change)) - del visited + if node.out_degree(): + sum_f = node.out_degree(weight=total_change) + new_delta = delta - sum_f + else: + new_delta = 0 + i += 1 + + + while i < MAX_ITERATIONS and abs(new_delta) > 0.00001: + change.a[:] = 0 + change[edge] = new_delta + + # start in one direction + + search.bfs_search(g, node, node_visitor) + change[edge] = 0 + + if plot: + ## Plot changes after forward run + g.ep.change.a[:] = change.a + flowmodeltestdata.plot_amounts(g,'plastic_deltas.png', 'change') + + + # now go downstream, if we started upstream + # (or upstream, if we started downstream) + node = reverse_graph(g, node_visitor, node_visitor2, edge) + if upstream: + sum_f = node.out_degree(weight=total_change) + \ + node.out_degree(weight=change) + else: + sum_f = node.in_degree(weight=total_change) + \ + node.in_degree(weight=change) + new_delta = delta - sum_f + change[edge] = new_delta + search.bfs_search(g, node, node_visitor) + + + if plot: + ## Plot changes after backward run + g.ep.change.a[:] = change.a + flowmodeltestdata.plot_amounts(g,'plastic_deltas.png', 'change') + + # balance out the changes + search.bfs_search(g, node, node_visitor2) + change[edge] = 0 + + if plot: + ## Plot changes after balancing + g.ep.change.a[:] = change.a + flowmodeltestdata.plot_amounts(g,'plastic_deltas.png', 'change') + + # add up the total changes + total_change.a += change.a + + node = reverse_graph(g, node_visitor, node_visitor2, edge) + + if plot: + ## Plot total changes + g.ep.change.a[:] = total_change.a + flowmodeltestdata.plot_amounts(g,f'plastic_deltas_{i}.png', 'change') + + if upstream: + if node.in_degree(): + sum_f = node.in_degree(weight=total_change) + new_delta = delta - sum_f + else: + new_delta = 0 + else: + if node.out_degree(): + sum_f = node.out_degree(weight=total_change) + new_delta = delta - sum_f + else: + new_delta = 0 + i += 1 + + # finally clean up g.set_reversed(False) g.clear_filters() - return change + return total_change + + +def reverse_graph(g, node_visitor: NodeVisitor, node_visitor2, edge): + g.set_reversed(not g.is_reversed()) + node_visitor.balance_factor = 1 / node_visitor.balance_factor + node = edge.target() if not g.is_reversed() else edge.source() + node_visitor.forward = not node_visitor.forward + node_visitor2.balance_factor = 1 / node_visitor2.balance_factor + return node class GraphWalker: def __init__(self, g): self.graph = gt.Graph(g) - self.edge_mask = self.graph.new_edge_property("bool") - def calculate(self, implementation_edges, formula: Formula): + def calculate(self, implementation_edges, deltas): """Calculate the changes on flows for a solution""" + # ToDo: deepcopy might be expensive. Why do we clone here? + # NOTE BD: initially the idea was that the this 'calculate' function + # returns a copy of the graph with the updated amounts. Needed to return + # an updated copy in order to compare this updated copy with the original + # graph, so we can say what was changed by the solution. + # For this, we need a deepcopy, otherwise the original graph would be + # overwritten. + # If it is OK to overwrite the amounts on the input graph because we + # have this data in the database so we can compare the output (right?), + # then no need to deepcopy. g = copy.deepcopy(self.graph) - g.ep.change.a[:] = 0 # store the changes for each actor to sum total in the end - changes_actors = [] - if formula.is_absolute: - total = sum(g.ep.amount[edge] for edge in implementation_edges) - new_total = formula.calculate(total) - #factor = formula.calculate_factor(total) - else: - factor = formula.calculate_factor() + overall_changes = None for i, edge in enumerate(implementation_edges): g.ep.include[edge] = True - start = time.time() - amount = g.ep.amount[edge] - if formula.is_absolute: - # distribute total change to changes on edges - # depending on share of total - solution_factor = new_total * amount / total - else: - solution_factor = factor + solution_delta = deltas[i] changes = traverse_graph(g, edge=edge, - solution=solution_factor, - amount=g.ep.amount) - changes_actors.append(changes) - self.graph.ep.include[edge] = False - end = time.time() - print(f'edge {i} - {end-start}s') - - if (i > 50): - break - - # we compute the solution for each distinct Actor-Actor flow in the - # implementation flows and assume that we can just sum the changes - # of this part to the changes of the previous part - for e in g.edges(): - # ToDo: optimize performance of summing changes (get rid of loops) - delta = sum(ch[e] for ch in changes_actors) - g.ep.amount[e] += delta - if (delta != 0): - g.ep.changed[e] = True + delta=solution_delta) + if overall_changes is None: + overall_changes = changes.a + else: + overall_changes += changes.a + g.ep.include[edge] = False + + if overall_changes is not None: + g.ep.amount.a += overall_changes + + has_changed = overall_changes != 0 + g.ep.changed.a[has_changed] = True + return g diff --git a/repair/apps/asmfa/migrations/0046_strategyfractionflow_process.py b/repair/apps/asmfa/migrations/0046_strategyfractionflow_process.py new file mode 100644 index 000000000..2c9d27567 --- /dev/null +++ b/repair/apps/asmfa/migrations/0046_strategyfractionflow_process.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-08-15 12:56 + +from django.db import migrations, models +import repair.apps.utils.protect_cascade + + +class Migration(migrations.Migration): + + dependencies = [ + ('asmfa', '0045_auto_20190626_0847'), + ] + + operations = [ + migrations.AddField( + model_name='strategyfractionflow', + name='process', + field=models.ForeignKey(null=True, on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, related_name='f_strategyfractionflowprocesses', to='asmfa.Process'), + ), + ] diff --git a/repair/apps/asmfa/migrations/0047_auto_20190902_1402.py b/repair/apps/asmfa/migrations/0047_auto_20190902_1402.py new file mode 100644 index 000000000..866ee4c25 --- /dev/null +++ b/repair/apps/asmfa/migrations/0047_auto_20190902_1402.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.4 on 2019-09-02 14:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('asmfa', '0046_strategyfractionflow_process'), + ] + + operations = [ + migrations.AddField( + model_name='strategyfractionflow', + name='hazardous', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='strategyfractionflow', + name='waste', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='fractionflow', + name='hazardous', + field=models.BooleanField(default=False), + ), + ] diff --git a/repair/apps/asmfa/migrations/0048_auto_20190923_1237.py b/repair/apps/asmfa/migrations/0048_auto_20190923_1237.py new file mode 100644 index 000000000..6a03f3d8e --- /dev/null +++ b/repair/apps/asmfa/migrations/0048_auto_20190923_1237.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-23 12:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('changes', '0046_auto_20190830_1048'), + ('asmfa', '0047_auto_20190902_1402'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='strategyfractionflow', + unique_together={('strategy', 'fractionflow')}, + ), + ] diff --git a/repair/apps/asmfa/models/flows.py b/repair/apps/asmfa/models/flows.py index c4f0cb73b..c827821ac 100644 --- a/repair/apps/asmfa/models/flows.py +++ b/repair/apps/asmfa/models/flows.py @@ -237,7 +237,7 @@ class FractionFlow(Flow): on_delete=models.SET_NULL, related_name='f_pub') avoidable = models.BooleanField(default=True) - hazardous = models.BooleanField(default=True) + hazardous = models.BooleanField(default=False) # composition related information nace = models.CharField(max_length=255, blank=True) @@ -249,13 +249,18 @@ class FractionFlow(Flow): class StrategyFractionFlow(GDSEModel): - strategy = models.ForeignKey(Strategy, - on_delete=models.CASCADE, - related_name='f_fractionflowstrategy') - fractionflow = models.ForeignKey(FractionFlow, - on_delete=models.CASCADE, + strategy = models.ForeignKey(Strategy, on_delete=models.CASCADE, + related_name='f_fractionflowstrategy') + fractionflow = models.ForeignKey(FractionFlow, on_delete=models.CASCADE, related_name='f_strategyfractionflow') amount = models.FloatField(default=0) - material = models.ForeignKey(Material, null=True, - on_delete=PROTECT_CASCADE, - related_name='f_strategyfractionflowmaterials') \ No newline at end of file + material = models.ForeignKey(Material, null=True, on_delete=PROTECT_CASCADE, + related_name='f_strategyfractionflowmaterials') + process = models.ForeignKey( + Process, null=True, on_delete=PROTECT_CASCADE, + related_name='f_strategyfractionflowprocesses') + waste = models.BooleanField(default=False) + hazardous = models.BooleanField(default=False) + + class Meta(GDSEModel.Meta): + unique_together = ('strategy', 'fractionflow') diff --git a/repair/apps/asmfa/models/nodes.py b/repair/apps/asmfa/models/nodes.py index a95ad868a..c3df29b6d 100644 --- a/repair/apps/asmfa/models/nodes.py +++ b/repair/apps/asmfa/models/nodes.py @@ -1,9 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import re + from django.db import models from django.utils.timezone import now from djmoney.models.fields import MoneyField +from django.core.exceptions import ValidationError from repair.apps.login.models import GDSEModel from repair.apps.asmfa.models.keyflows import KeyflowInCasestudy @@ -63,6 +66,39 @@ class Activity(Node): activitygroup = models.ForeignKey(ActivityGroup, on_delete=PROTECT_CASCADE) + @property + def nace_number(self) -> str: + """ + only the digits in the nace number, that have to be unique + within a keyflow + """ + regex='\d+' + match = re.findall(regex, self.nace) + nace_number = ''.join(match) + return nace_number + + def validate_unique(self, exclude=None): + super().validate_unique(exclude=exclude) + + # test if the number in the nace is unique + qs = self.__class__._default_manager.filter( + activitygroup__keyflow=self.activitygroup.keyflow) + + for row in qs: + if self.nace_number == row.nace_number: + + raise ValidationError( + f'Cannot create Actor with nace-code {self.nace}, ' + 'because there exists already an activity with the same ' + f'number {self.nace_number} in nace code {row.nace}' + ) + + def save(self, *args, **kwargs): + """Call :meth:`full_clean` before saving.""" + if self.pk is None: + self.full_clean() + super().save(*args, **kwargs) + class Reason(models.Model): """Reason for exclusion of actors""" diff --git a/repair/apps/asmfa/serializers/bulkcreate.py b/repair/apps/asmfa/serializers/bulkcreate.py index 979cd9706..eae2a471d 100644 --- a/repair/apps/asmfa/serializers/bulkcreate.py +++ b/repair/apps/asmfa/serializers/bulkcreate.py @@ -114,7 +114,8 @@ class Actor2ActorCreateSerializer(BulkSerializerMixin, referenced_model=Composition), 'source': Reference(name='publication', referenced_field='publication__citekey', - referenced_model=PublicationInCasestudy), + referenced_model=PublicationInCasestudy, + filter_args={'casestudy': '@casestudy'}), 'process': Reference(name='process', referenced_field='name', referenced_model=Process, allow_null=True), @@ -140,6 +141,7 @@ def validate(self, attrs): file_type=self.input_file_ext.replace('.', ''), encoding=self.encoding ) + error_mask.add_message(message) raise ValidationError( error_mask.messages, url ) @@ -168,7 +170,8 @@ class ActorStockCreateSerializer(BulkSerializerMixin, referenced_model=Composition), 'source': Reference(name='publication', referenced_field='publication__citekey', - referenced_model=PublicationInCasestudy), + referenced_model=PublicationInCasestudy, + filter_args={'casestudy': '@casestudy'}), 'amount': 'amount', 'year': 'year', 'waste': 'waste' @@ -243,7 +246,8 @@ class FractionCreateSerializer(BulkSerializerMixin, ProductFractionSerializer): 'hazardous': 'hazardous', 'source': Reference(name='publication', referenced_field='publication__citekey', - referenced_model=PublicationInCasestudy) + referenced_model=PublicationInCasestudy, + filter_args={'casestudy': '@casestudy'}) } def get_queryset(self): diff --git a/repair/apps/asmfa/tests/flowmodeltestdata.py b/repair/apps/asmfa/tests/flowmodeltestdata.py index 4eaea2a19..9ce2c87ff 100644 --- a/repair/apps/asmfa/tests/flowmodeltestdata.py +++ b/repair/apps/asmfa/tests/flowmodeltestdata.py @@ -1,5 +1,10 @@ # -*- coding: utf-8 -*- from collections import namedtuple +try: + import graph_tool as gt + import graph_tool.draw +except ModuleNotFoundError: + pass import numpy as np from django.test import TestCase @@ -20,8 +25,172 @@ from repair.apps.asmfa.models import Actor, Material, Actor2Actor -class GenerateBreadToBeerData(TestCase): +def _split_flows(G): + """Split the flows based on material composition + + If a flow is composed of different materials, it is split into individual flows per material + with the corresponding mass. + If the flow is composed of a single material, the composition property is removed and replaced with material. + """ + g = G.copy() + g.clear_edges() + del g.edge_properties['flow'] + eprops = G.edge_properties.keys() + mass_list = [] + material_list = [] + assert 'flow' in eprops, "The graph must have 'flow' edge property" + e_list = [] + for e in G.edges(): + prop = G.ep.flow[e] + assert isinstance(prop, + dict), "Edge property flow must be a dictionary in edge {}".format( + e) + for material, percent in prop['composition'].items(): + e_list.append(np.array([e.source(), e.target()], dtype=int)) + mass_list.append(float(prop['amount']) * float(percent)) + material_list.append(material) + e_array = np.vstack(e_list) + g.add_edge_list(e_array) + eprop_mass = g.new_edge_property("float", vals=mass_list) + eprop_material = g.new_edge_property("string", vals=material_list) + g.edge_properties['amount'] = eprop_mass + g.edge_properties['material'] = eprop_material + return g + +def bread_to_beer_graph(): + """Returns a graph-tool Graph for the BreadtoBeer case""" + G = gt.Graph(directed=True) + G.add_vertex(6) + vid = G.new_vertex_property("string") + G.vertex_properties["id"] = vid + G.vp.id[0] = 'Households' + G.vp.id[1] = 'Incineration' + G.vp.id[2] = 'Digester' + G.vp.id[3] = 'Brewery' + G.vp.id[4] = 'Supermarkets' + G.vp.id[5] = 'Farm' + flow = G.new_edge_property("object") + G.edge_properties["flow"] = flow + e = G.add_edge(G.vertex(0), G.vertex(1)) + G.ep.flow[e] = {'amount': 20, + 'composition': {'bread': 0.25, 'other waste': 0.75}} + e = G.add_edge(G.vertex(0), G.vertex(2)) + G.ep.flow[e] = {'amount': 10, 'composition': {'bread': 1.0}} + e = G.add_edge(G.vertex(3), G.vertex(2)) + G.ep.flow[e] = {'amount': 20, 'composition': {'sludge': 1.0}} + e = G.add_edge(G.vertex(3), G.vertex(4)) + G.ep.flow[e] = {'amount': 40, 'composition': {'beer': 1.0}} + e = G.add_edge(G.vertex(5), G.vertex(3)) + G.ep.flow[e] = {'amount': 9, 'composition': {'barley': 1.0}} + split = _split_flows(G) + return split + + +def plastic_package_graph(): + """Returns a graph-tool Graph for the plastic packaging case""" + G = gt.Graph(directed=True) + G.add_vertex(12) + vid = G.new_vertex_property("string") + G.vertex_properties["id"] = vid + G.vp.id[0] = 'Farm' + G.vp.id[1] = 'Packaging' + G.vp.id[2] = 'Oil rig' + G.vp.id[3] = 'Oil refinery' + G.vp.id[4] = 'Stock 1' + G.vp.id[5] = 'Production' + G.vp.id[6] = 'Consumption' + G.vp.id[7] = 'Waste' + G.vp.id[8] = 'Burn' + G.vp.id[9] = 'Recycling' + G.vp.id[10] = 'Stock 2' + G.vp.id[11] = 'Waste 2' + flow = G.new_edge_property("object") + eid = G.new_edge_property("int") # need a persistent edge id, because graph-tool can reindex the edges + G.edge_properties["flow"] = flow + G.edge_properties["eid"] = eid + e = G.add_edge(G.vertex(0), G.vertex(1)) + G.ep.flow[e] = {'amount': 95, + 'composition': {'cucumber': 0.3158, 'milk': 0.6842}} + G.ep.eid[e] = 0 + e = G.add_edge(G.vertex(2), G.vertex(3)) + G.ep.flow[e] = {'amount': 20, 'composition': {'crude oil': 1.0}} + G.ep.eid[e] = 1 + e = G.add_edge(G.vertex(3), G.vertex(4)) + G.ep.flow[e] = {'amount': 16, 'composition': {'petrol': 1.0}} + G.ep.eid[e] = 2 + e = G.add_edge(G.vertex(3), G.vertex(5)) + G.ep.flow[e] = {'amount': 4, 'composition': {'plastic': 1.0}} + G.ep.eid[e] = 3 + e = G.add_edge(G.vertex(5), G.vertex(1)) + G.ep.flow[e] = {'amount': 5, 'composition': {'plastic': 1.0}} + G.ep.eid[e] = 4 + e = G.add_edge(G.vertex(1), G.vertex(6)) + G.ep.flow[e] = {'amount': 100, + 'composition': {'plastic': 0.05, 'cucumber': 0.3, + 'milk': 0.65}} + G.ep.eid[e] = 5 + e = G.add_edge(G.vertex(6), G.vertex(7)) + G.ep.flow[e] = {'amount': 75, 'composition': {'human waste': 1.0}} + G.ep.eid[e] = 6 + e = G.add_edge(G.vertex(6), G.vertex(8)) + G.ep.flow[e] = {'amount': 3, 'composition': {'plastic': 1.0}} + G.ep.eid[e] = 7 + e = G.add_edge(G.vertex(6), G.vertex(9)) + G.ep.flow[e] = {'amount': 2, 'composition': {'plastic': 1.0}} + G.ep.eid[e] = 8 + e = G.add_edge(G.vertex(9), G.vertex(10)) + G.ep.flow[e] = {'amount': 1, 'composition': {'waste': 1.0}} + G.ep.eid[e] = 9 + e = G.add_edge(G.vertex(9), G.vertex(5)) + G.ep.flow[e] = {'amount': 1, 'composition': {'plastic': 1.0}} + G.ep.eid[e] = 10 + e = G.add_edge(G.vertex(6), G.vertex(11)) + G.ep.flow[e] = {'amount': 20, 'composition': {'other waste': 1.0}} + G.ep.eid[e] = 11 + split = _split_flows(G) + return split + + +def plot_amounts(g, file=None, prop='amount'): + """ + Plots the graph with the property defined by `prop` + on the edges into a file + + Parameters + ---------- + g : graph_tool.Graph + file : str + prop : str, optional (default='amount') + + """ + quantities = g.ep[prop] + vertex_ids = [f'{int(v)}' for v in g.vertices()] + vertex_text = g.new_vertex_property("string", vals=vertex_ids) + mass_text = g.new_edge_property("string", + vals=[str(round(i, 3)) for i in quantities]) + gt.draw.graph_draw(g, vertex_size=20, vertex_text=vertex_text, + vprops={"text_position": -1, + "font_size": 10}, + edge_text=mass_text, + output_size=(700, 600), inline=True, + output=file) + + +def plot_materials(g, file=None): + """Plots the graph with the 'material' property on the edges into a file""" + vertex_ids = [f'{int(v)}' for v in g.vertices()] + vertex_text = g.new_vertex_property("string", vals=vertex_ids) + gt.draw.graph_draw(g, vertex_size=20, vertex_text=vertex_text, + vprops={"text_position": -1, + "font_size": 10}, + edge_text=g.ep.material, + output_size=(700, 600), inline=True, + output=file) + + +class GenerateBreadToBeerData(TestCase): + """Uses models and factories to set up the test case""" @classmethod def setUpClass(cls): super().setUpClass() @@ -63,10 +232,10 @@ def setUpClass(cls): nace='A-0003', activitygroup=group_A) farming_activity = ActivityFactory( name='Farming', - nace='C-0000', activitygroup=group_C) + nace='C-0010', activitygroup=group_C) inciterator_activity = ActivityFactory( name='Incineration', - nace='C-0001', activitygroup=group_C) + nace='C-0011', activitygroup=group_C) ## Actors ## @@ -144,10 +313,10 @@ def setUpClass(cls): keyflow=cls.keyflow) -class GenerateTestDataMixin: - """ - Generate Testdata - """ +class GeneratePlasticPackagingData: + ''' + Generate test data for the cucumber-plastic packaging case. + ''' def create_keyflow(self): """Create the keyflow""" @@ -173,15 +342,15 @@ def create_materials(self): Mat('Human Waste', is_waste=True), Mat('Other Waste', is_waste=True) ] - + Frac = namedtuple('Fraction', ['composition', 'material', 'fraction']) - Frac.__new__.__defaults__ = (None, None, 0.0) + Frac.__new__.__defaults__ = (None, None, 0.0) fractions = [Frac('Packaged Milk', 'Milk', 0.25), Frac('Packaged Milk', 'Plastic', 0.75), Frac('Packaged Cucumber', 'Plastic', 0.15), Frac('Packaged Cucumber', 'Cucumber', 0.85) ] - + for mat in material_names: material = MaterialFactory( name=mat.name, @@ -190,7 +359,7 @@ def create_materials(self): Factory = WasteFactory if mat.is_waste else ProductFactory composition = Factory(name=mat.name) self.compositions[mat.name] = composition - + for frac in fractions: fraction = ProductFractionFactory( fraction=frac.fraction, @@ -305,9 +474,9 @@ def create_solutions(self): Solution([(1, "Crude Oil"), (2, "Crude Oil")],[],3), Solution([],[],4) ] - -class GenerateBigTestDataMixin(GenerateTestDataMixin): + +class GenerateBigTestDataMixin(GeneratePlasticPackagingData): """Big amount of Test Data""" def create_actors(self, n_actors=10000): activity_names = [ diff --git a/repair/apps/asmfa/tests/test_activitygroup.py b/repair/apps/asmfa/tests/test_activitygroup.py index 9905fd248..1cf2cfda9 100644 --- a/repair/apps/asmfa/tests/test_activitygroup.py +++ b/repair/apps/asmfa/tests/test_activitygroup.py @@ -34,9 +34,9 @@ def setUp(self): activitygroup=self.activitygroup1) self.activity2 = ActivityFactory(nace='NACE2', activitygroup=self.activitygroup1) - self.activity3 = ActivityFactory(nace='NACE1', + self.activity3 = ActivityFactory(nace='NACE3', activitygroup=self.activitygroup1) - self.activity4 = ActivityFactory(nace='NACE3', + self.activity4 = ActivityFactory(nace='NACE4', activitygroup=self.activitygroup2) def test_nace_codes(self): @@ -46,8 +46,8 @@ def test_nace_codes(self): """ self.assertSetEqual(set(self.activitygroup1.nace_codes), - {'NACE1', 'NACE2'}) - self.assertSetEqual(set(self.activitygroup2.nace_codes), {'NACE3'}) + {'NACE1', 'NACE2', 'NACE3'}) + self.assertSetEqual(set(self.activitygroup2.nace_codes), {'NACE4'}) def test_nace_code_serializer(self): """ @@ -58,8 +58,8 @@ def test_nace_code_serializer(self): kwargs={**self.url_pks, 'pk': self.activitygroup1.pk,} response = self.get_check_200(url, **kwargs) self.assertSetEqual(set(response.data['nace']), - {'NACE1', 'NACE2'}) + {'NACE1', 'NACE2', 'NACE3'}) kwargs={**self.url_pks, 'pk': self.activitygroup2.pk,} response = self.get_check_200(url, **kwargs) - self.assertSetEqual(set(response.data['nace']), {'NACE3'}) + self.assertSetEqual(set(response.data['nace']), {'NACE4'}) diff --git a/repair/apps/asmfa/tests/test_actors.py b/repair/apps/asmfa/tests/test_actors.py index 1fc3f0edd..b98dbc57a 100644 --- a/repair/apps/asmfa/tests/test_actors.py +++ b/repair/apps/asmfa/tests/test_actors.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from django.urls import reverse +from django.core.exceptions import ValidationError from test_plus import APITestCase from rest_framework import status from repair.tests.test import BasicModelPermissionTest, LoginTestCase @@ -10,6 +11,7 @@ ActorFactory, ReasonFactory, ) +from repair.apps.asmfa.models import ActivityGroup, Activity class TestActor(LoginTestCase, APITestCase): @@ -147,6 +149,27 @@ def setUp(self): activitygroup__keyflow=self.kic, activitygroup__id=self.activitygroup) + def test_unique_nacecode(self): + """Test if the nace-code number is unique""" + ag = ActivityGroup.objects.get(pk=self.activitygroup) + ag2 = ActivityGroupFactory(keyflow__id=44) + activity1 = Activity(activitygroup=ag, nace='E-01234', name='A1') + activity2 = Activity(activitygroup=ag, nace='E-01235', name='A2') + activity3 = Activity(activitygroup=ag, nace='V-01234', name='A3') + activity1.save() + activity2.save() + # saving activity3 should raise the Validation error, because + # the number 01234 already exists in nacecode E-01234 + with self.assertRaises(ValidationError): + activity3.save() + # in another keyflow, there may exist the same number once + activity4 = Activity(activitygroup=ag2, nace='V-01234', name='A3') + activity4.save() + # but not twice + activity5 = Activity(activitygroup=ag2, nace='G-01234', name='A3') + with self.assertRaises(ValidationError): + activity5.save() + class ActivitygroupInCaseStudyTest(BasicModelPermissionTest, APITestCase): diff --git a/repair/apps/asmfa/tests/test_bulk.py b/repair/apps/asmfa/tests/test_bulk.py index 23d56c78a..3d14cd09d 100644 --- a/repair/apps/asmfa/tests/test_bulk.py +++ b/repair/apps/asmfa/tests/test_bulk.py @@ -6,6 +6,7 @@ from django.urls import reverse from test_plus import APITestCase from rest_framework import status +from http.client import responses from repair.tests.test import LoginTestCase from django.urls import reverse import numpy as np @@ -121,7 +122,7 @@ def test_bulk_group(self): res = self.client.post(self.ag_url, data) res_json = res.json() - assert res.status_code == 201 + assert res.status_code == status.HTTP_201_CREATED assert res_json['count'] == len(file_codes) assert len(res_json['created']) == len(new_codes) @@ -146,7 +147,7 @@ def test_bulk_group_errors(self): 'bulk_upload' : open(file_path, 'rb'), } res = self.client.post(self.ag_url, data) - assert res.status_code == 400 + assert res.status_code == status.HTTP_400_BAD_REQUEST def test_bulk_activity_errors(self): file_path = os.path.join(os.path.dirname(__file__), @@ -156,7 +157,7 @@ def test_bulk_activity_errors(self): 'bulk_upload' : open(file_path, 'rb'), } res = self.client.post(self.ac_url, data) - assert res.status_code == 400 + assert res.status_code == status.HTTP_400_BAD_REQUEST def test_excel(self): file_path = os.path.join(os.path.dirname(__file__), @@ -166,7 +167,7 @@ def test_excel(self): 'bulk_upload' : open(file_path, 'rb'), } res = self.client.post(self.ac_url, data) - assert res.status_code == 400 + assert res.status_code == status.HTTP_400_BAD_REQUEST def test_bulk_activity(self): """Test bulk upload activities""" @@ -188,7 +189,7 @@ def test_bulk_activity(self): new_nace = [c for c in file_nace if str(c) not in existing_nace] res = self.client.post(self.ac_url, data) - assert res.status_code == 201 + assert res.status_code == status.HTTP_201_CREATED res_json = res.json() assert res_json['count'] == len(file_nace) assert len(res_json['created']) == len(new_nace) @@ -215,7 +216,8 @@ def test_bulk_actors(self): } res = self.client.post(self.actor_url, data) - assert res.status_code == 201 + assert res.status_code == status.HTTP_201_CREATED, ( + responses.get(res.status_code, res.status_code), res.content) def test_bulk_actor_errors(self): """Test that activity matches activitygroup""" @@ -227,7 +229,7 @@ def test_bulk_actor_errors(self): } res = self.client.post(self.actor_url, data) - assert res.status_code == 400 + assert res.status_code == status.HTTP_400_BAD_REQUEST def test_bulk_locations(self): """Test bulk upload actors""" @@ -242,7 +244,8 @@ def test_bulk_locations(self): } res = self.client.post(self.location_url, data) - assert res.status_code == 201 + assert res.status_code == status.HTTP_201_CREATED, ( + responses.get(res.status_code, res.status_code), res.content) lengths.append(len(AdministrativeLocation.objects.all())) assert lengths[0] == lengths[1] @@ -255,7 +258,7 @@ def test_bulk_locations(self): } res = self.client.post(self.location_url, data) - assert res.status_code == 400 + assert res.status_code == status.HTTP_400_BAD_REQUEST @skip('not implemented yet') def test_actor_matches_activity(self): @@ -355,7 +358,7 @@ def test_bulk_flow(self): } res = self.client.post(self.a2a_url, data) - assert res.status_code == 201 + assert res.status_code == status.HTTP_201_CREATED lengths.append(Actor2Actor.objects.count()) # check that 2nd loop does not create additional products # but updates them @@ -372,7 +375,7 @@ def test_bulk_flow(self): } res = self.client.post(self.a2a_url, data) - assert res.status_code == 400 + assert res.status_code == status.HTTP_400_BAD_REQUEST file_path = os.path.join(os.path.dirname(__file__), self.testdata_folder, @@ -382,7 +385,7 @@ def test_bulk_flow(self): } res = self.client.post(self.a2a_url, data) - assert res.status_code == 400 + assert res.status_code == status.HTTP_400_BAD_REQUEST def test_bulk_stock(self): """Test file-based upload of actor2actor""" @@ -395,7 +398,7 @@ def test_bulk_stock(self): res = self.client.post(self.astock_url, data) assert FractionFlow.objects.filter(to_stock=True).count() > 0 - assert res.status_code == 201 + assert res.status_code == status.HTTP_201_CREATED class BulkImportMaterialsTest(LoginTestCase, APITestCase): @@ -454,7 +457,7 @@ def test_bulk_materials(self): } res = self.client.post(self.mat_url, data) - assert res.status_code == 201 + assert res.status_code == status.HTTP_201_CREATED def test_bulk_materials_errors(self): file_path = os.path.join(os.path.dirname(__file__), @@ -466,7 +469,7 @@ def test_bulk_materials_errors(self): } res = self.client.post(self.mat_url, data) - assert res.status_code == 400 + assert res.status_code == status.HTTP_400_BAD_REQUEST n_after = len(Material.objects.all()) assert n_after == n_before @@ -479,7 +482,7 @@ def test_bulk_waste(self): } res = self.client.post(self.waste_url, data) - assert res.status_code == 201 + assert res.status_code == status.HTTP_201_CREATED def test_bulk_products(self): lengths = [] @@ -492,7 +495,7 @@ def test_bulk_products(self): } res = self.client.post(self.product_url, data) - assert res.status_code == 201 + assert res.status_code == status.HTTP_201_CREATED lengths.append(ProductFraction.objects.count()) # check that 2nd loop does not create additional fractions # but updates (kind of) @@ -517,7 +520,7 @@ def test_bulk_products(self): } res = self.client.post(self.product_url, data) - assert res.status_code == 400 + assert res.status_code == status.HTTP_400_BAD_REQUEST file_path = os.path.join(os.path.dirname(__file__), self.testdata_folder, @@ -527,7 +530,7 @@ def test_bulk_products(self): } res = self.client.post(self.product_url, data) - assert res.status_code == 400 + assert res.status_code == status.HTTP_400_BAD_REQUEST file_path = os.path.join(os.path.dirname(__file__), self.testdata_folder, @@ -537,5 +540,5 @@ def test_bulk_products(self): } res = self.client.post(self.product_url, data) - assert res.status_code == 400 + assert res.status_code == status.HTTP_400_BAD_REQUEST diff --git a/repair/apps/asmfa/tests/test_graph.py b/repair/apps/asmfa/tests/test_graph.py index f7b146a49..f83e29c22 100644 --- a/repair/apps/asmfa/tests/test_graph.py +++ b/repair/apps/asmfa/tests/test_graph.py @@ -1,13 +1,21 @@ import os from test_plus import APITestCase +from django.contrib.gis.geos import Polygon, Point, GeometryCollection +from django.db.models.functions import Coalesce +from django.db.models import Case, When, Value, F +from django.contrib.gis.geos import Polygon, MultiPolygon +from django.db.models import Sum +from django.test import TestCase + from repair.apps.asmfa.graphs.graph import BaseGraph, StrategyGraph +from repair.apps.asmfa.graphs.graphwalker import GraphWalker from repair.tests.test import LoginTestCase, AdminAreaTest - from repair.apps.asmfa.factories import (ActorFactory, ActivityFactory, ActivityGroupFactory, MaterialFactory, - FractionFlowFactory + FractionFlowFactory, + AdministrativeLocationFactory ) from repair.apps.changes.factories import (StrategyFactory, SolutionInStrategyFactory, @@ -16,17 +24,225 @@ SolutionPartFactory, ImplementationQuestionFactory, ImplementationQuantityFactory, - AffectedFlowFactory + AffectedFlowFactory, + FlowReferenceFactory, + ImplementationQuestionFactory, + ImplementationQuantityFactory, + KeyflowInCasestudyFactory, + PossibleImplementationAreaFactory ) -from repair.apps.changes.models import ImplementationQuantity -from repair.apps.asmfa.models import FractionFlow, StrategyFractionFlow +from repair.apps.asmfa.models import (Actor, FractionFlow, StrategyFractionFlow, + Activity, Material, KeyflowInCasestudy, + CaseStudy, Process) +from repair.apps.changes.models import (Solution, Strategy, + ImplementationQuantity, + SolutionInStrategy, Scheme, + ImplementationArea) from repair.apps.studyarea.factories import StakeholderFactory from repair.apps.login.factories import UserInCasestudyFactory -from django.contrib.gis.geos import Polygon, Point, GeometryCollection -from django.db.models.functions import Coalesce -class GraphTest(LoginTestCase, APITestCase): +from repair.apps.changes.tests.test_graphwalker import MultiplyTestDataMixin +from repair.apps.asmfa.tests import flowmodeltestdata + + +class GraphWalkerTest(TestCase): + + def test_data_creation(self): + b2b = flowmodeltestdata.bread_to_beer_graph() + assert b2b.num_vertices() == 6 + assert b2b.num_edges() == 6 + plastic = flowmodeltestdata.plastic_package_graph() + assert plastic.num_vertices() == 12 + assert plastic.num_edges() == 15 + + def test_plot(self): + b2b = flowmodeltestdata.bread_to_beer_graph() + flowmodeltestdata.plot_amounts(b2b, 'breadtobeer_amounts.png') + flowmodeltestdata.plot_materials(b2b, 'breadtobeer_materials.png') + plastic = flowmodeltestdata.plastic_package_graph() + flowmodeltestdata.plot_amounts(plastic, 'plastic_amounts.png') + flowmodeltestdata.plot_materials(plastic, 'plastic_materials.png') + + def test_plastic_packaging(self): + """Reduce plastic between Packaging->Consumption + + Results (change in tons): + Packaging --> Consumption -0.3 + Oil rig --> Oil refinery -0.3 + Oil refinery --> Production -0.24 + Production --> Packaging -0.3 + Consumption --> Burn -0.18 + Consumption --> Recycling -0.12 + Recycling --> Production -0.06 + """ + plastic = flowmodeltestdata.plastic_package_graph() + gw = GraphWalker(plastic) + change = gw.graph.new_edge_property('float') + gw.graph.edge_properties['change'] = change + changed = gw.graph.new_edge_property('bool', val=False) + gw.graph.edge_properties['changed'] = changed + include = gw.graph.new_edge_property('bool') + gw.graph.edge_properties['include'] = include + bf = gw.graph.new_vertex_property('float', val=1.0) + gw.graph.vertex_properties['downstream_balance_factor'] = bf + pe = gw.graph.edge(gw.graph.vertex(1), gw.graph.vertex(6), + all_edges=True) # the 3 edges between Packaging and Cosumption + implementation_edges = [e for e in pe + if gw.graph.ep.material[e] == 'plastic'] + # reduce the Plastic by 0.3 tons on the implementation_edge + deltas = [-0.3] + # select affected flows + for i, e in enumerate(gw.graph.edges()): + # flows of 'plastic' or 'crude oil' are affected by the solution + if gw.graph.ep.material[e] in ['plastic', 'crude oil']: + gw.graph.ep.include[e] = True + else: + gw.graph.ep.include[e] = False + result = gw.calculate(implementation_edges, deltas) + + # for each flow, test if the results are approximately + # the expected value, taking some delta due to the balancing + # into account + for i, e in enumerate(result.edges()): + print(f"{result.vp.id[e.source()]} --> {result.vp.id[e.target()]} " + f"/ {result.ep.material[e]}: {result.ep.amount[e]}") + if result.vp.id[e.source()] == 'Packaging' \ + and result.vp.id[e.target()] == 'Consumption' \ + and result.ep.material[e] == 'plastic': + expected = 5.0 - 0.3 + self.assertAlmostEqual( + result.ep.amount[e], expected, delta=0.2, + msg='Packaging->Consumption') + elif result.vp.id[e.source()] == 'Oil rig' \ + and result.vp.id[e.target()] == 'Oil refinery' \ + and result.ep.material[e] == 'crude oil': + expected = 20.0 - 0.3 + self.assertAlmostEqual( + result.ep.amount[e], expected, delta=0.2, + msg='Oil rig->Oil refinery') + elif result.vp.id[e.source()] == 'Oil refinery' \ + and result.vp.id[e.target()] == 'Production' \ + and result.ep.material[e] == 'plastic': + expected = 4.0 - 0.24 + self.assertAlmostEqual( + result.ep.amount[e], expected, delta=0.2, + msg='Oil refinery->Production') + elif result.vp.id[e.source()] == 'Production' \ + and result.vp.id[e.target()] == 'Packaging' \ + and result.ep.material[e] == 'plastic': + expected = 5.0 - 0.3 + self.assertAlmostEqual( + result.ep.amount[e], expected, delta=0.2, + msg='Production->Packaging') + elif result.vp.id[e.source()] == 'Consumption' \ + and result.vp.id[e.target()] == 'Burn' \ + and result.ep.material[e] == 'plastic': + expected = 3.0 - 0.18 + self.assertAlmostEqual( + result.ep.amount[e], expected, delta=0.1, + msg='Consumption->Burn') + elif result.vp.id[e.source()] == 'Consumption' \ + and result.vp.id[e.target()] == 'Recycling' \ + and result.ep.material[e] == 'plastic': + expected = 2.0 - 0.12 + self.assertAlmostEqual( + result.ep.amount[e], expected, delta=0.1, + msg='Consumption->Recycling') + elif result.vp.id[e.source()] == 'Recycling' \ + and result.vp.id[e.target()] == 'Production' \ + and result.ep.material[e] == 'plastic': + expected = 1.0 - 0.06 + self.assertAlmostEqual( + result.ep.amount[e], expected, delta=0.06, + msg='Recycling->Production') + else: + self.assertAlmostEqual(result.ep.amount[e], + gw.graph.ep.amount[e], + delta=result.ep.amount[e]/10) + + # test if the changes in all nodes are balanced + result.ep.change.a = result.ep.amount.a - gw.graph.ep.amount.a + + for u in result.vertices(): + # dangling nodes with no in- or outflows can be ignored + if not (u.in_degree() and u.out_degree()): + continue + # for the rest, the sum of the in-deltas should equal to the + # sum of the out-deltas, adjusted with the balancing factor + sum_in_deltas = u.in_degree(result.ep.change) + sum_out_deltas = u.out_degree(result.ep.change) + balanced_out_deltas = sum_out_deltas / bf[u] + balanced_delta = sum_in_deltas - balanced_out_deltas + self.assertAlmostEqual( + balanced_delta, 0, places=4, + msg=f'Node {int(u)} not balanced, deltas in: {sum_in_deltas}, ' + f'deltas out: {sum_out_deltas}, bf: {bf[u]}, ' + f'balanced deltas out: {balanced_out_deltas}' + ) + def test_milk_production(self): + """Reduce milk production between Farm->Packaging + + Results (change in tons): + Farm --> Packaging -26.0 + Packaging --> Consumption -26.0 + Consumption --> Waste -20.526315789473685 + Consumption --> Waste 2 -5.473684210526315 + """ + plastic = flowmodeltestdata.plastic_package_graph() + gw = GraphWalker(plastic) + change = gw.graph.new_edge_property('float') + gw.graph.edge_properties['change'] = change + changed = gw.graph.new_edge_property('bool', + vals=[False for e in range(gw.graph.num_edges())]) + gw.graph.edge_properties['changed'] = changed + include = gw.graph.new_edge_property('bool') + gw.graph.edge_properties['include'] = include + bf = gw.graph.new_vertex_property('float', + vals=[1.0 for v in range(gw.graph.num_vertices())]) + gw.graph.vertex_properties['downstream_balance_factor'] = bf + pe = gw.graph.edge(gw.graph.vertex(0), gw.graph.vertex(1), + all_edges=True) # the 2 edges between Farm and Packaging + implementation_edges = [e for e in pe + if gw.graph.ep.material[e] == 'milk'] + # reduce the milk production by 26.0 tons on the implementation_edge + deltas = [-26.0] + # select affected flows + for i, e in enumerate(gw.graph.edges()): + # these material flows are affected by the solution + if gw.graph.ep.material[e] in ['milk', 'human waste', 'other waste']: + gw.graph.ep.include[e] = True + else: + gw.graph.ep.include[e] = False + result = gw.calculate(implementation_edges, deltas) + for i, e in enumerate(result.edges()): + print(f"{result.vp.id[e.source()]} --> {result.vp.id[e.target()]} / {result.ep.material[e]}: {result.ep.amount[e]}") + if result.vp.id[e.source()] == 'Farm' \ + and result.vp.id[e.target()] == 'Packaging' \ + and result.ep.material[e] == 'milk': + expected = 65.0 - 26.0 + self.assertAlmostEqual(result.ep.amount[e], expected, places=2) + elif result.vp.id[e.source()] == 'Packaging' \ + and result.vp.id[e.target()] == 'Consumption' \ + and result.ep.material[e] == 'milk': + expected = 65.0 - 26.0 + self.assertAlmostEqual(result.ep.amount[e], expected, places=2) + elif result.vp.id[e.source()] == 'Consumption' \ + and result.vp.id[e.target()] == 'Waste' \ + and result.ep.material[e] == 'human waste': + expected = 75.0 - 20.526315789473685 + self.assertAlmostEqual(result.ep.amount[e], expected, places=2) + elif result.vp.id[e.source()] == 'Consumption' \ + and result.vp.id[e.target()] == 'Waste 2' \ + and result.ep.material[e] == 'other waste': + expected = 20.0 - 5.473684210526315 + self.assertAlmostEqual(result.ep.amount[e], expected, places=2) + else: + self.assertAlmostEqual(result.ep.amount[e], + gw.graph.ep.amount[e], places=2) + + +class GraphTest(LoginTestCase, APITestCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -41,240 +257,1135 @@ def setUp(self): activitygroup=self.activitygroup1) self.activity2 = ActivityFactory(nace='NACE2', activitygroup=self.activitygroup1) - self.activity3 = ActivityFactory(nace='NACE1', + self.activity3 = ActivityFactory(nace='NACE3', activitygroup=self.activitygroup1) - self.activity4 = ActivityFactory(nace='NACE3', + self.activity4 = ActivityFactory(nace='NACE4', activitygroup=self.activitygroup2) def test_graph(self): self.graph = BaseGraph(self.kic, tag='test') + class StrategyGraphTest(LoginTestCase, APITestCase): - stakeholdercategoryid = 48 - stakeholderid = 21 - strategyid = 1 - actor_originid = 1 - actor_old_targetid = 2 - actor_new_targetid = 3 - materialname = 'wool insulation' + fixtures = ['peelpioneer_data'] + + #fractionflows_count = 26 + + ##ToDo: set correct values for testing + #origin_actor_BvDid = 'SBC0011' + #new_destination_actor_BvDid = 'SBC0009' + #materialname = "Food Waste" + #fractionflows_count_for_test_actor = 2 + #amount_before_shift = 5 + #amount_after_shift = 4.75 @classmethod def setUpClass(cls): super().setUpClass() + cls.casestudy = CaseStudy.objects.get(name='SandboxCity') + cls.keyflow = KeyflowInCasestudy.objects.get( + casestudy=cls.casestudy, + keyflow__name='Food Waste') + cls.basegraph = BaseGraph(cls.keyflow, tag='unittest') + print('building basegraph') + cls.basegraph.build() + + cls.households = Activity.objects.get(nace='V-0000') + cls.collection = Activity.objects.get(nace='E-3811') + cls.treatment = Activity.objects.get(nace='E-3821') + cls.food_waste = Material.objects.get(name='Food Waste') + cls.orange_product = Material.objects.get(name='Essential Orange oils') + def setUp(self): super().setUp() - self.activitygroup1 = ActivityGroupFactory(name='MyGroup', - keyflow=self.kic) - self.activitygroup2 = ActivityGroupFactory(name='AnotherGroup', - keyflow=self.kic) - self.activity1 = ActivityFactory(nace='NACE1', - activitygroup=self.activitygroup1) - self.activity2 = ActivityFactory(nace='NACE2', - activitygroup=self.activitygroup1) - self.activity3 = ActivityFactory(nace='NACE1', - activitygroup=self.activitygroup1) - self.activity4 = ActivityFactory(nace='NACE3', - activitygroup=self.activitygroup2) + self.solution = SolutionFactory(solution_category__keyflow=self.keyflow) + self.possible_impl_area = PossibleImplementationAreaFactory( + solution=self.solution, + # should cover netherlands + geom=MultiPolygon( + Polygon(((2, 50), (2, 55), + (9, 55), (9, 50), (2, 50)))), + ) - stakeholder = StakeholderFactory( - id=self.stakeholderid, - stakeholder_category__id=self.stakeholdercategoryid, - stakeholder_category__casestudy=self.uic.casestudy, - ) - user = UserInCasestudyFactory(casestudy=self.kic.casestudy, - user__user__username='Hans Norbert') - ## generate a new strategy - self.strategy = StrategyFactory(id=self.strategyid, - keyflow=self.kic, - user=user, - name='Test Strategy') - - # Create a solution with 3 parts 2 questions - self.solution1 = SolutionFactory(name='Solution 1') - - question1 = ImplementationQuestionFactory( - question="What is the answer to life, the universe and everything?", - min_value=0.0, - max_value=10000.0, - step=0.01, - select_values='0.0,3.14,42,1234.43', - solution=self.solution1 - ) - question2 = ImplementationQuestionFactory( - question="What is 1 + 1?", - min_value=1, - max_value=1000, - step=1, - solution=self.solution1 - ) - - #self.solutionpart1 = SolutionPartFactory( - #solution=self.solution1, - #question=question1, - #a=0, - #b=1 - #) - #self.solutionpart2 = SolutionPartFactory( - #solution=self.solution1, - #question=question2 - #) - - # new origin with new actor - origin_activity = ActivityFactory(name='origin_activity') - origin_actor = ActorFactory(id=self.actor_originid, - name='origin_actor', - activity=origin_activity) - - # old target with actor - old_destination_activity = ActivityFactory( - name='old_destination_activity_activity') - old_destination_actor = ActorFactory(id=self.actor_old_targetid, - name='old_destination_actor', - activity=old_destination_activity) - - # new target with new actor - new_destination_activity = ActivityFactory(name='target_activity') - new_destination_actor = ActorFactory(id=self.actor_new_targetid, - name='new_destination_actor', - activity=new_destination_activity) - - # actor 11 - actor11 = ActorFactory(id=11, name='Actor11', - activity=old_destination_activity) - # actor 12 - actor12 = ActorFactory(id=12, name='Actor12', - activity=new_destination_activity) - - # new material - wool = MaterialFactory(name=self.materialname, - keyflow=self.kic) - - part_new_flow = SolutionPartFactory( - solution=self.solution1, - implementation_flow_origin_activity=origin_activity, - implementation_flow_destination_activity=old_destination_activity, - implementation_flow_material=wool, - #implementation_flow_process=, - question=question1, - a=1.0, - b=1.0, - implements_new_flow=True, - keep_origin=True, - new_target_activity=new_destination_activity, - map_request="pick an actor" - ) - - # create fraction flow - new_flow = FractionFlowFactory( - origin=origin_actor, - destination=old_destination_actor, - material=wool, - amount=1000, - keyflow=self.kic - ) - # create fraction flow 2 - new_flow2 = FractionFlowFactory( - origin=actor11, - destination=actor12, - material=wool, - amount=11000, - keyflow=self.kic - ) - - implementation_area = Polygon(((0.0, 0.0), (0.0, 20.0), (56.0, 20.0), - (56.0, 0.0), (0.0, 0.0))) - solution_in_strategy1 = SolutionInStrategyFactory( - solution=self.solution1, strategy=self.strategy, - geom=GeometryCollection(implementation_area), priority=0) - - # quantities are auto-generated, don't create new ones! - answer = ImplementationQuantity.objects.get( - question=question1, - implementation=solution_in_strategy1 - ) - answer.value = 1 - answer.save() - #answer = ImplementationQuantityFactory( - #question=question1, - #implementation=solution_in_strategy1, - #value=1.0) - - # create AffectedFlow - affected = AffectedFlowFactory( - solution_part=part_new_flow, - origin_activity=origin_activity, - destination_activity=new_destination_activity, - material=wool) - - #self.solution2 = SolutionFactory(name='Solution 2') - #solution_in_strategy2 = SolutionInStrategyFactory( - #solution=self.solution2, strategy=self.strategy, - #priority=1) - - base_graph = BaseGraph(self.kic, tag='test') - base_graph.remove() - base_graph.build() - base_graph.save() + def test_modify(self): + scheme = Scheme.MODIFICATION + factor = 2 - def test_graph(self): - return - self.graph = StrategyGraph(self.strategy, tag='test') - # delete stored graph file to test creation of data - self.graph.remove() - self.graph.build() - - assert len(FractionFlow.objects.all()) == 4 - - flows = FractionFlow.objects.filter( - origin_id=self.actor_originid, - destination_id=self.actor_new_targetid).annotate( - actual_amount=Coalesce('f_strategyfractionflow__amount', 'amount')) - - assert len(flows) == 1 - ff = flows[0] - assert ff.material.name == self.materialname - assert ff.destination.id == self.actor_new_targetid - #flow is split to new destination thus devided by 2 - assert ff.actual_amount == 500 - - # there is 1 strategyflows that sets the amount to 0 for the - # implementation_flow; no other strategyflows because we didnt include - # the flows in AffectedFlows - assert len(StrategyFractionFlow.objects.all()) == 1 - strategyflows = StrategyFractionFlow.objects.filter( - fractionflow__id=1, - material__name=self.materialname - ) - assert len(strategyflows) == 1 - assert strategyflows[0].amount == 0.0 - - # test again but now with loading the stored graph - self.graph.build() - - assert len(FractionFlow.objects.all()) == 4 - - flows = FractionFlow.objects.filter( - origin_id=self.actor_originid, - destination_id=self.actor_new_targetid).annotate( - actual_amount=Coalesce('f_strategyfractionflow__amount', 'amount')) - - assert len(flows) == 1 - ff = flows[0] - assert ff.material.name == self.materialname - assert ff.destination.id == self.actor_new_targetid - #flow is split to new destination thus devided by 2 - assert ff.actual_amount == 500 - - # there is 1 strategyflows that sets the amount to 0 for the - # implementation_flow; no other strategyflows because we didnt include - # the flows in AffectedFlows - assert len(StrategyFractionFlow.objects.all()) == 1 - strategyflows = StrategyFractionFlow.objects.filter( - fractionflow__id=1, - material__name=self.materialname - ) - assert len(strategyflows) == 1 - assert strategyflows[0].amount == 0.0 \ No newline at end of file + implementation_flow = FlowReferenceFactory( + origin_activity=self.households, + origin_area=self.possible_impl_area, + destination_activity=self.collection, + destination_area=self.possible_impl_area, + material=self.food_waste + ) + + flow_changes = FlowReferenceFactory( + material=self.orange_product, + waste=0 + ) + + # this should multiply the flow amounts by factor + mod_part = SolutionPartFactory( + solution=self.solution, + question=None, + flow_reference=implementation_flow, + flow_changes=flow_changes, + scheme=scheme, + is_absolute=False, + a = 0, + b = factor + ) + + AffectedFlowFactory( + origin_activity=self.collection, + destination_activity=self.treatment, + solution_part=mod_part, + material=self.food_waste + ) + + # create the implementation along with the strategy + implementation = SolutionInStrategyFactory( + strategy__keyflow=self.keyflow, + solution=self.solution + ) + + # set implementation area + implementation_area = ImplementationArea.objects.get( + implementation=implementation, + possible_implementation_area=self.possible_impl_area + ) + + # same as poss. impl. area, just for testing (you could also completely + # skip the implementation area, possible impl. area is sufficient + # for spatial filtering) + implementation_area.geom = self.possible_impl_area.geom + #MultiPolygon( + #Polygon(((2.5, 50.5), (2.5, 54.5), + #(8, 54.5), (8, 50.5), (2.5, 50.5)))) + implementation_area.save() + + sg = StrategyGraph( + implementation.strategy, + self.basegraph.tag) + + sg.build() + + # validate outcome + + impl_flows = FractionFlow.objects.filter( + origin__activity=self.households, + destination__activity=self.collection, + material=self.food_waste, + strategy__isnull=True, + ) + + affected_flows = FractionFlow.objects.filter( + origin__activity=self.collection, + destination__activity=self.treatment, + material=self.food_waste, + strategy__isnull=True, + ) + + impl_changes = StrategyFractionFlow.objects.filter( + fractionflow__in=impl_flows, + strategy=implementation.strategy) + + aff_changes = StrategyFractionFlow.objects.filter( + fractionflow__in=affected_flows, + strategy=implementation.strategy) + + materials = impl_changes.values_list('material', flat=True).distinct() + waste = impl_changes.values_list('waste', flat=True).distinct() + assert (len(materials) == 1 and + materials[0] == self.orange_product.id), ( + "The material was supposed to change but didn't " + "change in database") + assert (len(waste) == 1 and + waste[0] == False), ( + "The flows were supposed to change from from product to " + "waste but didn't change in database") + + # the origin flows are all in the netherlands + # and impl. area covers all of the netherlands -> all should be changed + assert len(impl_flows) == len(impl_changes), ( + f'There are {len(impl_flows)} implementation flows ' + f'and {len(impl_changes)} changes to those. ' + f'There should be one changed flow per implementation flow') + + impl_old_sum = impl_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + impl_new_sum = impl_changes.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + + self.assertAlmostEqual(impl_new_sum, impl_old_sum * factor, + msg=(f'new sum: {impl_new_sum}, ' + f'old sum:{impl_old_sum}, factor: {factor}') + ) + + # ToDo: those tests only work for the fixed test-set, not work + # for the randomly extendet dataset, because not all affected flows are + # actually affected (not connected to impl. flows) + # what can we test here + + #assert len(affected_flows) == len(aff_changes), ( + #f'There are {len(affected_flows)} affected flows ' + #f'and {len(aff_changes)} changes to those. ' + #f'There should be one changed flow per affected flow') + + aff_old_sum = affected_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + aff_new_sum = aff_changes.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + + #self.assertAlmostEqual(aff_new_sum, + #(impl_new_sum - impl_old_sum) + aff_old_sum) + + + def test_shift_destination(self): + scheme = Scheme.SHIFTDESTINATION + + factor = 0.2 + + implementation_flow = FlowReferenceFactory( + origin_activity=self.households, + destination_activity=self.collection, + material=self.food_waste + ) + + # shift from collection to treatment + shift = FlowReferenceFactory( + destination_activity=self.treatment, + destination_area=self.possible_impl_area, + material=self.orange_product, + waste=0 + ) + + # shift half of the amount + shift_part = SolutionPartFactory( + solution=self.solution, + question=None, + flow_reference=implementation_flow, + flow_changes=shift, + scheme=scheme, + is_absolute=False, + a = 0, + b = factor + ) + + # create the implementation along with the strategy + implementation = SolutionInStrategyFactory( + strategy__keyflow=self.keyflow, + solution=self.solution + ) + + sg = StrategyGraph( + implementation.strategy, + self.basegraph.tag) + + sg.build() + + status_quo_flows = FractionFlow.objects.filter( + origin__activity=self.households, + destination__activity=self.collection, + material=self.food_waste, + strategy__isnull=True + ) + changes = StrategyFractionFlow.objects.filter( + fractionflow__in=status_quo_flows, + strategy=implementation.strategy) + + new_flows = FractionFlow.objects.filter( + origin__activity=self.households, + destination__activity=self.treatment, + material=self.orange_product, + strategy=implementation.strategy + ) + + assert len(status_quo_flows) == len(new_flows) + + materials = new_flows.values_list('material', flat=True).distinct() + waste = new_flows.values_list('waste', flat=True).distinct() + assert (len(materials) == 1 and + materials[0] == self.orange_product.id), ( + "The material was supposed to change but didn't " + "change in database") + assert (len(waste) == 1 and + waste[0] == False), ( + "The flows were supposed to change from from product to " + "waste but didn't change in database") + + # original flows should have been reduced + old_sum = status_quo_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + new_sum = changes.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + new_flow_sum = new_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + + msg = f'new sum is {new_sum}, expected: {old_sum-old_sum*factor}' + self.assertAlmostEqual(new_sum, old_sum-old_sum*factor, msg=msg) + msg = f'new_flow_sum: {new_flow_sum} should be the difference of old_sum: {old_sum} - new_sum: {new_sum}' + self.assertAlmostEqual(new_flow_sum, old_sum - new_sum, msg=msg) + + # ToDo: additional asserts, affected flows + + def test_shift_origin(self): + scheme = Scheme.SHIFTORIGIN + + factor = 0.5 + + implementation_flow = FlowReferenceFactory( + origin_activity=self.households, + destination_activity=self.treatment, + material=self.food_waste + ) + + # shift from households to collection + shift = FlowReferenceFactory( + origin_activity=self.collection, + destination_area=self.possible_impl_area, + ) + + # shift half of the amount + shift_part = SolutionPartFactory( + solution=self.solution, + question=None, + flow_reference=implementation_flow, + flow_changes=shift, + scheme=scheme, + is_absolute=False, + a = 0, + b = factor + ) + + # create the implementation along with the strategy + implementation = SolutionInStrategyFactory( + strategy__keyflow=self.keyflow, + solution=self.solution + ) + + sg = StrategyGraph( + implementation.strategy, + self.basegraph.tag) + + sg.build() + + status_quo_flows = FractionFlow.objects.filter( + origin__activity=self.households, + destination__activity=self.treatment, + material=self.food_waste, + strategy__isnull=True + ) + changes = StrategyFractionFlow.objects.filter( + fractionflow__in=status_quo_flows) + + new_flows = FractionFlow.objects.filter( + origin__activity=self.collection, + destination__activity=self.treatment, + material=self.food_waste, + strategy=implementation.strategy + ) + + assert len(status_quo_flows) == len(new_flows) + + # original flows should have been reduced + old_sum = status_quo_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + new_sum = changes.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + new_flow_sum = new_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + + self.assertAlmostEqual(new_sum, old_sum - old_sum * factor) + self.assertAlmostEqual(new_flow_sum, old_sum - new_sum) + + def test_new_flows(self): + scheme = Scheme.NEW + + new_flow = FlowReferenceFactory( + origin_activity=self.collection, + destination_activity=self.treatment, + material=self.food_waste + ) + + amount = 1000 + + new_part = SolutionPartFactory( + solution=self.solution, + question=None, + flow_changes=new_flow, + scheme=scheme, + is_absolute=True, + a = 0, + b = amount + ) + + # create the implementation along with the strategy + implementation = SolutionInStrategyFactory( + strategy__keyflow=self.keyflow, + solution=self.solution + ) + + sg = StrategyGraph( + implementation.strategy, + self.basegraph.tag) + + sg.build() + + changes = StrategyFractionFlow.objects.all() + + assert not changes, ( + f'there should be no changes, ' + f'but there are {len(changes)} changed flows') + + new_flows = FractionFlow.objects.filter( + origin__activity=self.collection, + destination__activity=self.treatment, + material=self.food_waste, + strategy=implementation.strategy + ) + + new_sum = new_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + msg = (f'new_flow should have the amount of {amount} of the strategy, ' + f'but has an amount of {new_sum} ' + f"and values of {new_flows.values_list('amount', flat=True)}") + self.assertAlmostEqual(new_sum, amount, msg=msg) + + # ToDo: asserts, affected flows + + def test_prepend(self): + scheme = Scheme.PREPEND + + factor = 0.3 + + implementation_flow = FlowReferenceFactory( + origin_activity=self.collection, + destination_activity=self.treatment, + material=self.food_waste + ) + + # shift from collection to treatment + prefix = FlowReferenceFactory( + origin_activity=self.households, + origin_area=self.possible_impl_area, + material=self.orange_product, + waste=0 + ) + + # shift half of the amount + shift_part = SolutionPartFactory( + solution=self.solution, + question=None, + flow_reference=implementation_flow, + flow_changes=prefix, + scheme=scheme, + is_absolute=False, + a = 0, + b = factor + ) + + # create the implementation along with the strategy + implementation = SolutionInStrategyFactory( + strategy__keyflow=self.keyflow, + solution=self.solution + ) + + sg = StrategyGraph( + implementation.strategy, + self.basegraph.tag) + + sg.build() + + status_quo_flows = FractionFlow.objects.filter( + origin__activity=self.collection, + destination__activity=self.treatment, + material=self.food_waste, + strategy__isnull=True + ) + + new_flows = FractionFlow.objects.filter( + origin__activity=self.households, + destination__activity=self.collection, + material=self.orange_product, + strategy=implementation.strategy + ) + + sq_sum = status_quo_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + prep_sum = new_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + + assert len(status_quo_flows) == len(new_flows) + self.assertAlmostEqual( + prep_sum, sq_sum * factor, + msg=f'new flows sum up to {prep_sum}, expected: {sq_sum * factor}') + + materials = new_flows.values_list('material', flat=True).distinct() + waste = new_flows.values_list('waste', flat=True).distinct() + assert (len(materials) == 1 and + materials[0] == self.orange_product.id), ( + "The material was supposed to change but didn't " + "change in database") + assert (len(waste) == 1 and + waste[0] == False), ( + "The flows were supposed to change from from product to " + "waste but didn't change in database") + + # ToDo: additional asserts (test origins/destinations), affected flows + + def test_append(self): + scheme = Scheme.APPEND + + factor = 0.8 + + implementation_flow = FlowReferenceFactory( + origin_activity=self.households, + destination_activity=self.collection, + material=self.food_waste + ) + + # shift from collection to treatment + appendix = FlowReferenceFactory( + destination_activity=self.treatment, + destination_area=self.possible_impl_area, + ) + + # shift half of the amount + shift_part = SolutionPartFactory( + solution=self.solution, + question=None, + flow_reference=implementation_flow, + flow_changes=appendix, + scheme=scheme, + is_absolute=False, + a = 0, + b = factor + ) + + # create the implementation along with the strategy + implementation = SolutionInStrategyFactory( + strategy__keyflow=self.keyflow, + solution=self.solution + ) + + sg = StrategyGraph( + implementation.strategy, + self.basegraph.tag) + + sg.build() + + status_quo_flows = FractionFlow.objects.filter( + origin__activity=self.households, + destination__activity=self.collection, + material=self.food_waste, + strategy__isnull=True + ) + + changed_flows = StrategyFractionFlow.objects.filter( + fractionflow__in=status_quo_flows) + + new_flows = FractionFlow.objects.filter( + origin__activity=self.collection, + destination__activity=self.treatment, + material=self.food_waste, + strategy=implementation.strategy + ) + + sq_sum = status_quo_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + app_sum = new_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + + assert len(status_quo_flows) == len(new_flows) + self.assertAlmostEqual(app_sum, sq_sum * factor) + + # ToDo: additional asserts (test origins/destinations), affected flows + + +class PeelPioneerTest(LoginTestCase, APITestCase): + fixtures = ['peelpioneer_data'] + + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.casestudy = CaseStudy.objects.get(name='SandboxCity') + cls.keyflow = KeyflowInCasestudy.objects.get( + casestudy=cls.casestudy, + keyflow__name='Food Waste') + cls.basegraph = BaseGraph(cls.keyflow, tag='unittest') + cls.basegraph.build() + + cls.restaurants = Activity.objects.get(nace='I-5610') + cls.retail_food = Activity.objects.get(nace='G-4711') + cls.treatment_nonhazardous = Activity.objects.get(nace='E-3821') + cls.treatment_hazardous = Activity.objects.get(nace='E-3822') + cls.processing = Activity.objects.get(nace='C-1030') + cls.pharma_manufacture = Activity.objects.get(nace='C-2110') + cls.textile_manufacture = Activity.objects.get(nace='C-1399') + cls.retail_cosmetics = Activity.objects.get(nace='G-4775') + cls.petroleum_manufacture = Activity.objects.get(nace='C-1920') + cls.road_transport = Activity.objects.get(nace='H-4941') + cls.other_transport = Activity.objects.get(nace='H-5229') + + cls.food_waste = Material.objects.get(name='Food Waste') + cls.organic_waste = Material.objects.get(name='Organic Waste') + cls.orange_peel = Material.objects.get(name='Orange Peel') + cls.essential_oils = Material.objects.get(name='Essential Orange oils') + cls.fiber = Material.objects.get(name='Orange fibers') + cls.biofuel = Material.objects.get(name='Biofuel') + + cls.incineration = Process.objects.get(name='Incineration') + + def setUp(self): + super().setUp() + self.solution = SolutionFactory(solution_category__keyflow=self.keyflow) + + # create the implementation along with the strategy + self.implementation = SolutionInStrategyFactory( + strategy__keyflow=self.keyflow, + solution=self.solution + ) + + def test_solution(self): + + original_flow_count = FractionFlow.objects.count() + original_strat_flow_count = StrategyFractionFlow.objects.count() + + new_flow_count = 0 + new_strat_flow_count = 0 + + question = ImplementationQuestionFactory( + question='How much orange peel waste will be used?', + min_value=0, + max_value=1, + is_absolute=False, + solution=self.solution + ) + + answer = ImplementationQuantityFactory( + question=question, + value=1, + implementation=self.implementation + ) + + priority = 0 + + def affect_biofuel_chain(solpart): + '''add affected biofuel flows to given solution part''' + AffectedFlowFactory( + origin_activity=self.treatment_nonhazardous, + destination_activity=self.petroleum_manufacture, + solution_part=solpart, + material=self.biofuel + ) + AffectedFlowFactory( + origin_activity=self.petroleum_manufacture, + destination_activity=self.road_transport, + solution_part=solpart, + material=self.biofuel + ) + AffectedFlowFactory( + origin_activity=self.petroleum_manufacture, + destination_activity=self.other_transport, + solution_part=solpart, + material=self.biofuel + ) + AffectedFlowFactory( + origin_activity=self.road_transport, + destination_activity=self.treatment_hazardous, + solution_part=solpart, + material=self.biofuel + ) + AffectedFlowFactory( + origin_activity=self.other_transport, + destination_activity=self.treatment_hazardous, + solution_part=solpart, + material=self.biofuel + ) + + ### shift food waste from treatment to processing ### + ### -> new orange peel flows ### + + restaurants_to_treat = FlowReferenceFactory( + origin_activity=self.restaurants, + destination_activity=self.treatment_nonhazardous, + material=self.food_waste + ) + + retail_to_treat = FlowReferenceFactory( + origin_activity=self.retail_food, + destination_activity=self.treatment_nonhazardous, + material=self.food_waste + ) + + # ToDo: change process? + shift_to_processing = FlowReferenceFactory( + destination_activity=self.processing, + material=self.orange_peel + ) + + # part to shift flows from restaurants + part1 = SolutionPartFactory( + name='shift flows from restaurants', + solution=self.solution, + question=question, + flow_reference=restaurants_to_treat, + flow_changes=shift_to_processing, + scheme=Scheme.SHIFTDESTINATION, + a=0.05, + b=0, + priority=priority + ) + priority += 1 + + affect_biofuel_chain(part1) + + # part to shift flows from retail + part2 = SolutionPartFactory( + name='shift flows from retail', + solution=self.solution, + question=question, + flow_reference=retail_to_treat, + flow_changes=shift_to_processing, + scheme=Scheme.SHIFTDESTINATION, + a=0.05, + b=0, + priority=priority + ) + priority += 1 + + affect_biofuel_chain(part2) + + ### prepend flows to the orange peel flows ### + + # Warning: if there would already be orange peel coming from restaurants + # or retail to processing it takes 50% of all of those + # (not only the new ones) + + rest_to_proc = FlowReferenceFactory( + origin_activity=self.restaurants, + destination_activity=self.processing, + material=self.orange_peel + ) + + retail_to_proc = FlowReferenceFactory( + origin_activity=self.retail_food, + destination_activity=self.processing, + material=self.orange_peel + ) + + append_treatment = FlowReferenceFactory( + destination_activity=self.treatment_nonhazardous, + material=self.organic_waste, + waste=1 + ) + + # part to append to restaurant-processing flows going to treatment + part3 = SolutionPartFactory( + name='append to restaurant->processing -> treatment', + solution=self.solution, + flow_reference=rest_to_proc, + flow_changes=append_treatment, + scheme=Scheme.APPEND, + b=0.5, + priority=priority + ) + priority += 1 + + affect_biofuel_chain(part3) + + # part to append flows to retail-processing flows going to treatment + part4 = SolutionPartFactory( + name='append to retail->processing -> treatment', + solution=self.solution, + flow_reference=retail_to_proc, + flow_changes=append_treatment, + scheme=Scheme.APPEND, + b=0.5, + priority=priority + ) + priority += 1 + + affect_biofuel_chain(part4) + + append_textile = FlowReferenceFactory( + destination_activity=self.textile_manufacture, + material=self.fiber, + waste=0 + ) + + # part to append to restaurant-processing flows going to textile manu. + part5 = SolutionPartFactory( + name='append to restaurant->processing -> textile', + solution=self.solution, + flow_reference=rest_to_proc, + flow_changes=append_textile, + scheme=Scheme.APPEND, + b=0.03, + priority=priority + ) + priority += 1 + + # part to append to retail-processing flows going to textile manu. + part6 = SolutionPartFactory( + name='append to retail->processing -> textile', + solution=self.solution, + flow_reference=retail_to_proc, + flow_changes=append_textile, + scheme=Scheme.APPEND, + b=0.03, + priority=priority + ) + priority += 1 + + append_pharma = FlowReferenceFactory( + destination_activity=self.pharma_manufacture, + material=self.essential_oils, + waste=0 + ) + + # part to append to restaurant-processing flows going to pharma + part7 = SolutionPartFactory( + name='append to restaurant->processing -> pharma', + solution=self.solution, + flow_reference=rest_to_proc, + flow_changes=append_pharma, + scheme=Scheme.APPEND, + b=0.01, + priority=priority + ) + priority += 1 + + AffectedFlowFactory( + origin_activity=self.pharma_manufacture, + destination_activity=self.retail_cosmetics, + solution_part=part7, + material=self.essential_oils + ) + + # part to append to retail-processing flows going to pharma + part8 = SolutionPartFactory( + name='append to retail->processing -> pharma', + solution=self.solution, + flow_reference=retail_to_proc, + flow_changes=append_pharma, + scheme=Scheme.APPEND, + b=0.01, + priority=priority + ) + priority += 1 + + AffectedFlowFactory( + origin_activity=self.pharma_manufacture, + destination_activity=self.retail_cosmetics, + solution_part=part8, + material=self.essential_oils + ) + + # build graph and calculate strategy + + sg = StrategyGraph( + self.implementation.strategy, + self.basegraph.tag) + + sg.build() + + ### check shift from restaurants to processing ### + + sq_rest_to_treat = FractionFlow.objects.filter( + origin__activity=self.restaurants, + destination__activity=self.treatment_nonhazardous, + strategy__isnull=True + ) + strat_flows = StrategyFractionFlow.objects.filter( + strategy=self.implementation.strategy, + fractionflow__in=sq_rest_to_treat + ) + new_strat_flow_count += strat_flows.count() + new_rest_to_proc = FractionFlow.objects.filter( + strategy=self.implementation.strategy, + origin__activity=self.restaurants, + destination__activity=self.processing + ) + new_flow_count += new_rest_to_proc.count() + + sq_rest_to_treat_sum = sq_rest_to_treat.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + strat_sum = strat_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + new_rest_to_proc_sum = new_rest_to_proc.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + + self.assertAlmostEqual(strat_sum, sq_rest_to_treat_sum * 0.95) + self.assertAlmostEqual(new_rest_to_proc_sum, + sq_rest_to_treat_sum * 0.05) + + for strat_flow in strat_flows: + sq_amount = strat_flow.fractionflow.amount + origin = strat_flow.fractionflow.origin + process = strat_flow.fractionflow.process + new_flow = new_rest_to_proc.get(origin=origin, process=process) + self.assertAlmostEqual(strat_flow.amount, + sq_amount - sq_amount * 0.05) + self.assertAlmostEqual(new_flow.amount, sq_amount * 0.05) + assert new_flow.material == self.orange_peel + assert new_flow.waste == True + + ### check shift from retail to processing ### + + sq_retail_to_treat = FractionFlow.objects.filter( + origin__activity=self.retail_food, + destination__activity=self.treatment_nonhazardous, + strategy__isnull=True + ) + strat_flows = StrategyFractionFlow.objects.filter( + strategy=self.implementation.strategy, + fractionflow__in=sq_retail_to_treat + ) + new_strat_flow_count += strat_flows.count() + new_retail_to_proc = FractionFlow.objects.filter( + strategy=self.implementation.strategy, + origin__activity=self.retail_food, + destination__activity=self.processing + ) + new_flow_count += new_retail_to_proc.count() + + sq_retail_to_treat_sum = sq_retail_to_treat.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + strat_sum = strat_flows.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + new_retail_to_proc_sum = new_retail_to_proc.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + + self.assertAlmostEqual(strat_sum, sq_retail_to_treat_sum * 0.95) + self.assertAlmostEqual(new_retail_to_proc_sum, + sq_retail_to_treat_sum * 0.05) + + for strat_flow in strat_flows: + sq_amount = strat_flow.fractionflow.amount + origin = strat_flow.fractionflow.origin + process = strat_flow.fractionflow.process + new_flow = new_retail_to_proc.get(origin=origin, process=process) + self.assertAlmostEqual(strat_flow.amount, + sq_amount - sq_amount * 0.05) + self.assertAlmostEqual(new_flow.amount, sq_amount * 0.05) + assert new_flow.material == self.orange_peel + assert new_flow.waste == True + + ### check processing to treatment ### + + new_proc_to_treat = FractionFlow.objects.filter( + strategy=self.implementation.strategy, + origin__activity=self.processing, + destination__activity=self.treatment_nonhazardous + ) + new_flow_count += new_proc_to_treat.count() + + new_proc_to_treat_sum = new_proc_to_treat.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + + self.assertAlmostEqual( + new_proc_to_treat_sum, + (new_rest_to_proc_sum + new_retail_to_proc_sum) * 0.5) + + materials = new_proc_to_treat.values_list( + 'material', flat=True).distinct() + waste = new_proc_to_treat.values_list( + 'waste', flat=True).distinct() + assert (len(materials) == 1 and materials[0] == self.organic_waste.id) + assert (len(waste) == 1 and waste[0] == True) + + ### check processing to textile manufacture ### + + new_proc_to_textile = FractionFlow.objects.filter( + strategy=self.implementation.strategy, + origin__activity=self.processing, + destination__activity=self.textile_manufacture + ) + new_flow_count += new_proc_to_textile.count() + + new_proc_to_textile_sum = new_proc_to_textile.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + + self.assertAlmostEqual( + new_proc_to_textile_sum, + (new_rest_to_proc_sum + new_retail_to_proc_sum) * 0.03) + + materials = new_proc_to_textile.values_list( + 'material', flat=True).distinct() + waste = new_proc_to_textile.values_list( + 'waste', flat=True).distinct() + assert (len(materials) == 1 and materials[0] == self.fiber.id) + assert (len(waste) == 1 and waste[0] == False) + + ### check processing to pharma manufacture ### + + new_proc_to_pharma = FractionFlow.objects.filter( + strategy=self.implementation.strategy, + origin__activity=self.processing, + destination__activity=self.pharma_manufacture + ) + new_flow_count += new_proc_to_pharma.count() + + new_proc_to_pharma_sum = new_proc_to_pharma.aggregate( + sum_amount=Sum('amount'))['sum_amount'] + + self.assertAlmostEqual( + new_proc_to_pharma_sum, + (new_rest_to_proc_sum + new_retail_to_proc_sum) * 0.01) + + materials = new_proc_to_pharma.values_list( + 'material', flat=True).distinct() + waste = new_proc_to_pharma.values_list( + 'waste', flat=True).distinct() + assert (len(materials) == 1 and materials[0] == self.essential_oils.id) + assert (len(waste) == 1 and waste[0] == False) + + ### check affected flows ### + + sq_petroleum_flows = FractionFlow.objects.filter( + origin__activity=self.treatment_nonhazardous, + destination__activity=self.petroleum_manufacture, + material=self.biofuel, + strategy__isnull=True + ) + affected_petroleum = StrategyFractionFlow.objects.filter( + fractionflow__in=sq_petroleum_flows) + + biodigester = Actor.objects.get(BvDid='SBC0010') + sq_in_digester = FractionFlow.objects.filter( + destination=biodigester, + strategy__isnull=True) + sq_out_digester = FractionFlow.objects.filter( + origin=biodigester, + strategy__isnull=True) + strat_in_digester = FractionFlow.objects.filter( + destination=biodigester, + ).annotate( + s_amount=Coalesce('f_strategyfractionflow__amount', 'amount') + ) + strat_out_digester = FractionFlow.objects.filter( + origin=biodigester, + ).annotate( + s_amount=Coalesce('f_strategyfractionflow__amount', 'amount') + ) + sq_in_digester_sum = sq_in_digester.aggregate( + amount=Sum('amount'))['amount'] + sq_out_digester_sum = sq_out_digester.aggregate( + amount=Sum('amount'))['amount'] + strat_in_digester_sum = strat_in_digester.aggregate( + amount=Sum('s_amount'))['amount'] + strat_out_digester_sum = strat_out_digester.aggregate( + amount=Sum('s_amount'))['amount'] + assert sq_in_digester_sum > strat_in_digester_sum, ( + 'the input to treatment should be reduced in strategy') + + sq_digest_factor = sq_out_digester_sum / sq_in_digester_sum + strat_digest_factor = strat_out_digester_sum / strat_in_digester_sum + + self.assertAlmostEqual(sq_digest_factor, strat_digest_factor, + places=1, + msg=f'the factor at actor {biodigester} in ' + 'strategy is not the same as in status quo') + + def assert_balance_factor(activity): + actors = Actor.objects.filter(activity=activity) + for actor in actors: + in_flows = FractionFlow.objects.filter(destination=actor).annotate( + strategy_amount=Coalesce('f_strategyfractionflow__amount', + 'amount'), + statusquo_amount=Case( + When(strategy__isnull=False, then=0), + default=F('amount') + ) + ) + out_flows = FractionFlow.objects.filter(origin=actor).annotate( + strategy_amount=Coalesce('f_strategyfractionflow__amount', + 'amount'), + statusquo_amount=Case( + When(strategy__isnull=False, then=0), + default=F('amount') + ) + ) + if not (out_flows and in_flows): + continue + sq_in = in_flows.aggregate(amount=Sum('statusquo_amount'))['amount'] + sq_out = out_flows.aggregate(amount=Sum('statusquo_amount'))['amount'] + sf_in = in_flows.aggregate(amount=Sum('strategy_amount'))['amount'] + sf_out = out_flows.aggregate(amount=Sum('strategy_amount'))['amount'] + sq_factor = (sq_out / sq_in) if sq_out and sq_in else 1 + sf_factor = (sf_out / sf_in) if sf_out and sf_in else 1 + self.assertAlmostEqual(sq_factor, sf_factor, 1, + msg='the balance factor at actor ' + f'{actor} in strategy is not the ' + 'same as in status quo') + + assert_balance_factor(self.treatment_nonhazardous) + assert_balance_factor(self.petroleum_manufacture) + assert_balance_factor(self.road_transport) + assert_balance_factor(self.other_transport) + assert_balance_factor(self.treatment_hazardous) + + treat_non_out = FractionFlow.objects.filter( + origin__activity=self.treatment_nonhazardous).annotate( + strategy_amount=Coalesce('f_strategyfractionflow__amount', + 'amount')) + treat_haz_in = FractionFlow.objects.filter( + destination__activity=self.treatment_hazardous).annotate( + strategy_amount=Coalesce('f_strategyfractionflow__amount', + 'amount')) + treat_non_out_sum = treat_non_out.aggregate( + sq_amount=Sum('amount'), strat_amount=Sum('strategy_amount')) + treat_non_out_delta = (treat_non_out_sum['strat_amount'] - + treat_non_out_sum['sq_amount']) + treat_haz_in_sum = treat_haz_in.aggregate( + sq_amount=Sum('amount'), strat_amount=Sum('strategy_amount')) + treat_haz_in_delta = (treat_haz_in_sum['strat_amount'] - + treat_haz_in_sum['sq_amount']) + + self.assertAlmostEqual( + treat_non_out_delta * 0.2, treat_haz_in_delta, + msg=f'change of out-flow sum {treat_non_out_delta} ' + 'of non hazardous waste treatment should be 5 ' + f'times the change of in-flow sum {treat_haz_in_delta}' + 'hazardous waste treatment') + + ## all are affected (and not more than one per flow created) + #assert len(sq_petroleum_flows) == len(affected_petroleum) + #sq_petroleum_sum = sq_petroleum_flows.aggregate( + #amount=Sum('amount'))['amount'] + #aff_petroleum_sum = affected_petroleum.aggregate( + #amount=Sum('amount'))['amount'] + #assert sq_petroleum_sum > aff_petroleum_sum, ( + #'the affected flows to petroleum manufacture should be reduced ' + #'in strategy') + + sq_cosmetic_flows = FractionFlow.objects.filter( + origin__activity=self.pharma_manufacture, + destination__activity=self.retail_cosmetics) + affected_cosmetics = StrategyFractionFlow.objects.filter( + fractionflow__in=sq_cosmetic_flows) + + # ToDo: what do we expect here? + assert len(affected_cosmetics) == 0 + + ### check that there are no other flows affected ### + + assert FractionFlow.objects.count() == ( + new_flow_count + original_flow_count) + # ToDo: count recently added affected flows + #assert StrategyFractionFlow.objects.count() == ( + #sq_rest_to_treat.count() + sq_retail_to_treat.count() + + #sq_petroleum_flows.count() + sq_cosmetic_flows.count()) + #assert StrategyFractionFlow.objects.count() == ( + #new_strat_flow_count + affected_petroleum.count()) + + +class StrategyGraphPerformanceTest(MultiplyTestDataMixin, + StrategyGraphTest): + """The same tests but with bigger data""" diff --git a/repair/apps/asmfa/views/flowfilter.py b/repair/apps/asmfa/views/flowfilter.py index ade6d3180..1d28dc4c2 100644 --- a/repair/apps/asmfa/views/flowfilter.py +++ b/repair/apps/asmfa/views/flowfilter.py @@ -1,28 +1,28 @@ from collections import defaultdict, OrderedDict from rest_framework.viewsets import ModelViewSet from reversion.views import RevisionMixin -from rest_framework.response import Response +from django.contrib.gis.geos import GEOSGeometry from django.http import HttpResponseBadRequest, HttpResponse -from django.db.models.functions import Coalesce -from django.db.models import (Q, Subquery, Min, IntegerField, OuterRef, Sum, F, - Case, When, Value) -import time import numpy as np import copy import json from collections import defaultdict, OrderedDict from django.utils.translation import ugettext_lazy as _ from django.contrib.gis.db.models import Union +from rest_framework.response import Response +from rest_framework.decorators import action +from django.db.models import (Q, Sum, F, QuerySet) from repair.apps.utils.views import (CasestudyViewSetMixin, ModelPermissionViewSet, PostGetViewMixin) -from repair.apps.utils.utils import descend_materials +from repair.apps.utils.utils import (descend_materials, + get_annotated_fractionflows) from repair.apps.asmfa.models import ( Flow, AdministrativeLocation, Actor2Actor, Group2Group, Material, FractionFlow, Actor, ActivityGroup, Activity, - AdministrativeLocation, Process + AdministrativeLocation, Process, StrategyFractionFlow ) from repair.apps.changes.models import Strategy from repair.apps.studyarea.models import Area @@ -85,10 +85,39 @@ class FilterFlowViewSet(PostGetViewMixin, RevisionMixin, #additional_filters = {'origin__included': True, #'destination__included': True} + @action(methods=['get', 'post'], detail=False) + def count(self, request, **kwargs): + query_params = request.query_params.copy() + material = query_params.pop('material', None) + include_children = query_params.pop('include_child_materials', None) + queryset = self._filter(kwargs, query_params=query_params) + + if (material is not None): + mat_id = material[0] + if include_children: + mats = Material.objects.filter( + id__in=descend_materials([Material.objects.get(id=mat_id)])) + queryset = queryset.filter(strategy_material__in=mats) + else: + queryset = queryset.filter(strategy_material=mat_id) + + if ('origin_area' in request.data): + geojson = self.request.data['origin_area'] + poly = GEOSGeometry(geojson) + queryset = queryset.filter( + origin__administrative_location__geom__intersects=poly) + if ('destination_area' in request.data): + geojson = self.request.data['destination_area'] + poly = GEOSGeometry(geojson) + queryset = queryset.filter( + destination__administrative_location__geom__intersects=poly) + json = {'count': queryset.count()} + return Response(json) + def get_queryset(self): keyflow_pk = self.kwargs.get('keyflow_pk') - flows = FractionFlow.objects.filter(keyflow__id=keyflow_pk) - return flows.order_by('origin', 'destination') + strategy = self.request.query_params.get('strategy', None) + return get_annotated_fractionflows(keyflow_pk, strategy_id=strategy) # POST is used to send filter parameters not to create def post_get(self, request, **kwargs): @@ -188,7 +217,8 @@ def post_get(self, request, **kwargs): mats = descend_materials(list(materials) + list(unaltered_materials)) - queryset = queryset.filter(material__id__in=mats) + queryset = queryset.filter( + strategy_material__in=Material.objects.filter(id__in=mats)) agg_map = None try: @@ -208,6 +238,13 @@ def _filter(self, lookup_args, query_params=None, SerializerClass=None): if query_params: query_params = query_params.copy() strategy = query_params.pop('strategy', None) + for key in query_params: + if (key.startswith('material') or + key.startswith('waste') or + key.startswith('hazardous') or + key.startswith('process') or + key.startswith('amount')): + query_params['strategy_'+key] = query_params.pop(key)[0] # rename filter for stock, as this relates to field with stock pk # but serializer returns boolean in this field (if it is stock) stock_filter = query_params.pop('stock', None) @@ -215,28 +252,6 @@ def _filter(self, lookup_args, query_params=None, SerializerClass=None): query_params['to_stock'] = stock_filter[0] queryset = super()._filter(lookup_args, query_params=query_params, SerializerClass=SerializerClass) - if strategy: - # ToDo: material - queryset = queryset.filter( - ( - Q(f_strategyfractionflow__isnull = True) | - Q(f_strategyfractionflow__strategy = strategy[0]) - ) - ).annotate( - # strategy fraction flow overrides amounts - strategy_amount=Coalesce( - 'f_strategyfractionflow__amount', 'amount'), - # set new flow amounts to zero for status quo - statusquo_amount=Case( - When(strategy__isnull=True, then=F('amount')), - default=Value(0), - ) - ) - else: - # flows without filters for status quo - queryset = queryset.filter(strategy__isnull=True) - # just for convenience, use field statusquo_amount - queryset = queryset.annotate(statusquo_amount=F('amount')) return queryset def list(self, request, **kwargs): @@ -263,7 +278,7 @@ def map_aggregation(queryset, materials, unaltered_materials=[]): # workaround: reset order to avoid Django ORM bug with determining # distinct values in ordered querysets queryset = queryset.order_by() - materials_used = queryset.values('material').distinct() + materials_used = queryset.values('strategy_material').distinct() materials_used = Material.objects.filter(id__in=materials_used) # no materials given -> aggregate to top level if not materials: @@ -297,8 +312,8 @@ def map_aggregation(queryset, materials, unaltered_materials=[]): if not found: exclusion.append(flow.id) - # exclude flows not in material hierarchy, shouldn't happen if correctly - # filtered before, but doesn't hurt + # exclude flows not in material hierarchy, shouldn't happen if + # correctly filtered before, but doesn't hurt filtered = queryset.exclude(id__in=exclusion) return agg_map @@ -338,14 +353,16 @@ def serialize(self, queryset, origin_model=Actor, destination_model=Actor, destination_level = LEVEL_KEYWORD[destination_model] data = [] origins = origin_model.objects.filter( - id__in=queryset.values(origin_filter)) + id__in=list(queryset.values_list(origin_filter, flat=True))) destinations = destination_model.objects.filter( - id__in=queryset.values(destination_filter)) + id__in=list(queryset.values_list(destination_filter, flat=True))) # workaround Django ORM bug queryset = queryset.order_by() - groups = queryset.values(origin_filter, destination_filter, - 'waste', 'process', 'to_stock').distinct() + groups = queryset.values( + origin_filter, destination_filter, + 'strategy_waste', 'strategy_process', 'to_stock', + 'strategy_hazardous').distinct() def get_code_field(model): if model == Actor: @@ -366,8 +383,6 @@ def get_code_field(model): for group in groups: grouped = queryset.filter(**group) - # sum over all rows in group - total_amount = list(grouped.aggregate(Sum('statusquo_amount')).values())[0] origin_item = origin_dict[group[origin_filter]] origin_item['level'] = origin_level dest_item = destination_dict[group[destination_filter]] @@ -375,26 +390,20 @@ def get_code_field(model): dest_item['level'] = destination_level # sum up same materials annotation = { - 'name': F('material__name'), - 'level': F('material__level'), - 'statusquo_amount': Sum('statusquo_amount') + 'material': F('strategy_material'), + 'name': F('strategy_material_name'), + 'level': F('strategy_material_level'), + #'delta': Sum('strategy_delta'), + 'amount': Sum('strategy_amount') } - if strategy is not None: - total_strategy_amount = \ - list(grouped.aggregate(Sum('strategy_amount')).values())[0] - annotation['strategy_amount'] = F('strategy_amount') - # F('amount') takes Sum annotation instead of real field - annotation['delta'] = (F('strategy_amount') - - F('statusquo_amount')) grouped_mats = \ - list(grouped.values('material').annotate(**annotation)) + list(grouped.values('strategy_material').annotate(**annotation)) # aggregate materials according to mapping aggregation_map if aggregation_map: aggregated = {} for grouped_mat in grouped_mats: - mat_id = grouped_mat['material'] - amount = grouped_mat['statusquo_amount'] if not strategy \ - else grouped_mat['strategy_amount'] + mat_id = grouped_mat['strategy_material'] + amount = grouped_mat['amount'] mapped = aggregation_map[mat_id] agg_mat_ser = aggregated.get(mapped.id, None) @@ -404,34 +413,35 @@ def get_code_field(model): 'material': mapped.id, 'name': mapped.name, 'level': mapped.level, - 'amount': amount + 'amount': amount, + #'delta': grouped_mat['delta'] } - # take the amount in strategy if strategy was passed - if strategy is not None: - agg_mat_ser['delta'] = grouped_mat['delta'] aggregated[mapped.id] = agg_mat_ser # just sum amounts up if dict is already there else: agg_mat_ser['amount'] += amount - if strategy is not None: - agg_mat_ser['delta'] += grouped_mat['delta'] + #if strategy is not None: + #agg_mat_ser['delta'] += grouped_mat['delta'] grouped_mats = aggregated.values() - process = Process.objects.get(id=group['process']) \ - if group['process'] else None + process = Process.objects.get(id=group['strategy_process']) \ + if group['strategy_process'] else None + # sum over all rows in group + #sq_total_amount = list(grouped.aggregate(Sum('amount')).values())[0] + strat_total_amount = list( + grouped.aggregate(Sum('strategy_amount')).values())[0] + #deltas = list(grouped.aggregate(Sum('strategy_delta')).values())[0] flow_item = OrderedDict(( ('origin', origin_item), ('destination', dest_item), - ('waste', group['waste']), + ('waste', group['strategy_waste']), + ('hazardous', group['strategy_hazardous']), ('stock', group['to_stock']), ('process', process.name if process else ''), ('process_id', process.id if process else None), - ('amount', total_amount), - ('materials', grouped_mats) + ('amount', strat_total_amount), + ('materials', grouped_mats), + #('delta', deltas) )) - if strategy is not None: - flow_item['amount'] = total_strategy_amount - flow_item['delta'] = total_strategy_amount - total_amount - data.append(flow_item) return data diff --git a/repair/apps/asmfa/views/flows.py b/repair/apps/asmfa/views/flows.py index 93efaaf2e..4c04eb8be 100644 --- a/repair/apps/asmfa/views/flows.py +++ b/repair/apps/asmfa/views/flows.py @@ -114,7 +114,8 @@ def get_queryset(self): all().defer( "keyflow__note", "keyflow__casestudy__geom", - "keyflow__casestudy__focusarea") + "keyflow__casestudy__focusarea").\ + order_by('id') class GroupStockViewSet(StockViewSet): diff --git a/repair/apps/asmfa/views/keyflows.py b/repair/apps/asmfa/views/keyflows.py index 7ca855642..f86845dc9 100644 --- a/repair/apps/asmfa/views/keyflows.py +++ b/repair/apps/asmfa/views/keyflows.py @@ -8,7 +8,6 @@ DjangoFilterBackend, Filter, FilterSet, MultipleChoiceFilter) from django.core.exceptions import ObjectDoesNotExist from rest_framework.decorators import action - from rest_framework.response import Response import json @@ -67,7 +66,7 @@ class KeyflowInCasestudyViewSet(CasestudyViewSetMixin, ModelPermissionViewSet): queryset = KeyflowInCasestudy.objects.order_by('id') serializer_class = KeyflowInCasestudySerializer serializers = {'create': KeyflowInCasestudyPostSerializer, - 'update': KeyflowInCasestudyPostSerializer, } + 'update': KeyflowInCasestudyPostSerializer} @action(methods=['get', 'post'], detail=True) def build_graph(self, request, **kwargs ): diff --git a/repair/apps/asmfa/views/nodes.py b/repair/apps/asmfa/views/nodes.py index ce6a1d46d..d47e8c09e 100644 --- a/repair/apps/asmfa/views/nodes.py +++ b/repair/apps/asmfa/views/nodes.py @@ -3,6 +3,8 @@ from django.contrib.gis.geos import GEOSGeometry from django.db.models import CharField, Value from django.db.models import Q, Count +from rest_framework.decorators import action +from rest_framework.response import Response from repair.apps.asmfa.models import ( ActivityGroup, @@ -124,3 +126,9 @@ def get_queryset(self): actors = actors.annotate( flow_count=Count('outputs') + Count('inputs')) return actors.order_by('id') + + @action(methods=['get', 'post'], detail=False) + def count(self, request, **kwargs): + queryset = self._filter(kwargs, query_params=request.query_params) + json = {'count': queryset.count()} + return Response(json) diff --git a/repair/apps/changes/factories.py b/repair/apps/changes/factories.py index 47d3a5d86..a18c6425a 100644 --- a/repair/apps/changes/factories.py +++ b/repair/apps/changes/factories.py @@ -1,4 +1,4 @@ -from django.contrib.gis.geos.point import Point +from django.contrib.gis.geos import Polygon, MultiPolygon import factory from factory.django import DjangoModelFactory @@ -8,6 +8,7 @@ ActivityFactory, MaterialFactory, ProcessFactory, FractionFlowFactory) from repair.apps.asmfa.models import StrategyFractionFlow +from repair.apps.changes.models import Scheme from . import models @@ -29,6 +30,7 @@ class Meta: class ImplementationQuestionFactory(DjangoModelFactory): class Meta: model = models.ImplementationQuestion + solution = factory.SubFactory(SolutionFactory) question = 'How many percent are going to be saved?' min_value = 0.1 max_value = 0.5 @@ -36,19 +38,40 @@ class Meta: is_absolute = False +class PossibleImplementationAreaFactory(DjangoModelFactory): + class Meta: + model = models.PossibleImplementationArea + geom = MultiPolygon( + Polygon(((11, 11), (11, 12), (12, 12), (11, 11))), + Polygon(((12, 12), (12, 13), (13, 13), (12, 12))) + ) + question = 'Where are the actors located?' + solution = factory.SubFactory(SolutionFactory) + + +class FlowReferenceFactory(DjangoModelFactory): + class Meta: + model = models.FlowReference + origin_activity = None + destination_activity = None + material = None + process = None + origin_area = None + destination_area = None + waste = -1 + hazardous = -1 + + class SolutionPartFactory(DjangoModelFactory): class Meta: model = models.SolutionPart solution = factory.SubFactory(SolutionFactory) - implements_new_flow = False - implementation_flow_origin_activity = factory.SubFactory(ActivityFactory) - implementation_flow_destination_activity = \ - factory.SubFactory(ActivityFactory) - implementation_flow_material = factory.SubFactory(MaterialFactory) - implementation_flow_process = factory.SubFactory(ProcessFactory) - implementation_flow_spatial_application = 1 + scheme = Scheme.MODIFICATION + flow_reference = factory.SubFactory(FlowReferenceFactory) + flow_changes = None + priority = 0 - question = factory.SubFactory(ImplementationQuestionFactory) + question = None a = 0 b = 1 @@ -68,6 +91,7 @@ class Meta: strategy = factory.SubFactory(StrategyFactory) fractionflow = factory.SubFactory(FractionFlowFactory) material = factory.SubFactory(MaterialFactory) + process = None amount = 0.0 @@ -88,12 +112,27 @@ def participants(self, create, extracted, **kwargs): for participant in extracted: self.participants.add(participant) + class ImplementationQuantityFactory(DjangoModelFactory): class Meta: model = models.ImplementationQuantity implementation = factory.SubFactory(SolutionInStrategyFactory) question = factory.SubFactory(ImplementationQuestionFactory) - + value = 0 + + +class ImplementationAreaFactory(DjangoModelFactory): + class Meta: + model = models.ImplementationArea + implementation = factory.SubFactory(SolutionInStrategyFactory) + possible_implementation_area = factory.SubFactory( + PossibleImplementationAreaFactory) + geom = MultiPolygon( + Polygon(((11, 11), (11, 12), (12, 12), (11, 11))), + Polygon(((12, 12), (12, 13), (13, 13), (12, 12))) + ) + + class AffectedFlowFactory(DjangoModelFactory): class Meta: model = models.AffectedFlow diff --git a/repair/apps/changes/migrations/0042_auto_20190802_1415.py b/repair/apps/changes/migrations/0042_auto_20190802_1415.py new file mode 100644 index 000000000..b3d542a0d --- /dev/null +++ b/repair/apps/changes/migrations/0042_auto_20190802_1415.py @@ -0,0 +1,77 @@ +# Generated by Django 2.2.1 on 2019-08-02 12:15 + +import django.contrib.gis.db.models.fields +from django.db import migrations, models +import django.db.models.deletion +import repair.apps.login.models.bases +import repair.apps.utils.protect_cascade + + +class Migration(migrations.Migration): + + dependencies = [ + ('changes', '0041_auto_20190701_1143'), + ] + + operations = [ + migrations.CreateModel( + name='ImplementationArea', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('geom', django.contrib.gis.db.models.fields.MultiPolygonField(blank=True, null=True, srid=4326)), + ], + options={ + 'abstract': False, + 'default_permissions': ('add', 'change', 'delete', 'view'), + }, + bases=(repair.apps.login.models.bases.GDSEModelMixin, models.Model), + ), + migrations.CreateModel( + name='PossibleImplementationArea', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('geom', django.contrib.gis.db.models.fields.MultiPolygonField(srid=4326)), + ('name', models.TextField(default='')), + ], + options={ + 'abstract': False, + 'default_permissions': ('add', 'change', 'delete', 'view'), + }, + bases=(repair.apps.login.models.bases.GDSEModelMixin, models.Model), + ), + migrations.RemoveField( + model_name='solution', + name='possible_implementation_area', + ), + migrations.RemoveField( + model_name='solutioninstrategy', + name='geom', + ), + migrations.RemoveField( + model_name='solutionpart', + name='references_part', + ), + migrations.DeleteModel( + name='ActorInSolutionPart', + ), + migrations.AddField( + model_name='possibleimplementationarea', + name='solution', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='possible_implementation_area', to='changes.Solution'), + ), + migrations.AddField( + model_name='implementationarea', + name='implementation', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='implementation_area', to='changes.SolutionInStrategy'), + ), + migrations.AddField( + model_name='implementationarea', + name='possible_implementation_area', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='changes.PossibleImplementationArea'), + ), + migrations.AddField( + model_name='solutionpart', + name='possible_implementation_area', + field=models.ForeignKey(null=True, on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, to='changes.PossibleImplementationArea'), + ), + ] diff --git a/repair/apps/changes/migrations/0043_auto_20190805_1413.py b/repair/apps/changes/migrations/0043_auto_20190805_1413.py new file mode 100644 index 000000000..b0f50d554 --- /dev/null +++ b/repair/apps/changes/migrations/0043_auto_20190805_1413.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-08-05 12:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('changes', '0042_auto_20190802_1415'), + ] + + operations = [ + migrations.RenameField( + model_name='possibleimplementationarea', + old_name='name', + new_name='question', + ), + ] diff --git a/repair/apps/changes/migrations/0044_solutionpart_scheme.py b/repair/apps/changes/migrations/0044_solutionpart_scheme.py new file mode 100644 index 000000000..50852ddb9 --- /dev/null +++ b/repair/apps/changes/migrations/0044_solutionpart_scheme.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.1 on 2019-08-06 09:44 + +from django.db import migrations +import enumfields.fields +import repair.apps.changes.models.solutions + + +class Migration(migrations.Migration): + + dependencies = [ + ('changes', '0043_auto_20190805_1413'), + ] + + operations = [ + migrations.AddField( + model_name='solutionpart', + name='scheme', + field=enumfields.fields.EnumIntegerField(default=0, enum=repair.apps.changes.models.solutions.Scheme), + ), + ] diff --git a/repair/apps/changes/migrations/0045_auto_20190806_1421.py b/repair/apps/changes/migrations/0045_auto_20190806_1421.py new file mode 100644 index 000000000..41e9c228a --- /dev/null +++ b/repair/apps/changes/migrations/0045_auto_20190806_1421.py @@ -0,0 +1,117 @@ +# Generated by Django 2.2.1 on 2019-08-06 12:21 + +from django.db import migrations, models +import django.db.models.deletion +import repair.apps.login.models.bases +import repair.apps.utils.protect_cascade + + +class Migration(migrations.Migration): + + dependencies = [ + ('asmfa', '0045_auto_20190626_0847'), + ('changes', '0044_solutionpart_scheme'), + ] + + operations = [ + migrations.RemoveField( + model_name='solutionpart', + name='implementation_flow_destination_activity', + ), + migrations.RemoveField( + model_name='solutionpart', + name='implementation_flow_material', + ), + migrations.RemoveField( + model_name='solutionpart', + name='implementation_flow_origin_activity', + ), + migrations.RemoveField( + model_name='solutionpart', + name='implementation_flow_process', + ), + migrations.RemoveField( + model_name='solutionpart', + name='implementation_flow_solution_part', + ), + migrations.RemoveField( + model_name='solutionpart', + name='implementation_flow_spatial_application', + ), + migrations.RemoveField( + model_name='solutionpart', + name='implements_new_flow', + ), + migrations.RemoveField( + model_name='solutionpart', + name='keep_origin', + ), + migrations.RemoveField( + model_name='solutionpart', + name='map_request', + ), + migrations.RemoveField( + model_name='solutionpart', + name='new_material', + ), + migrations.RemoveField( + model_name='solutionpart', + name='new_target_activity', + ), + migrations.RemoveField( + model_name='solutionpart', + name='possible_implementation_area', + ), + migrations.AlterField( + model_name='affectedflow', + name='destination_activity', + field=models.ForeignKey(on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, related_name='affected_destinations', to='asmfa.Activity'), + ), + migrations.AlterField( + model_name='affectedflow', + name='material', + field=models.ForeignKey(on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, related_name='affected_materials', to='asmfa.Material'), + ), + migrations.AlterField( + model_name='affectedflow', + name='origin_activity', + field=models.ForeignKey(on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, related_name='affected_origins', to='asmfa.Activity'), + ), + migrations.AlterField( + model_name='affectedflow', + name='process', + field=models.ForeignKey(null=True, on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, related_name='affected_processes', to='asmfa.Process'), + ), + migrations.AlterField( + model_name='affectedflow', + name='solution_part', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='affected_flows', to='changes.SolutionPart'), + ), + migrations.CreateModel( + name='FlowReference', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('destination_activity', models.ForeignKey(null=True, on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, related_name='reference_destination', to='asmfa.Activity')), + ('destination_area', models.ForeignKey(null=True, on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, related_name='possible_destination_area', to='changes.PossibleImplementationArea')), + ('material', models.ForeignKey(null=True, on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, to='asmfa.Material')), + ('origin_activity', models.ForeignKey(null=True, on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, related_name='reference_origin', to='asmfa.Activity')), + ('origin_area', models.ForeignKey(null=True, on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, related_name='possible_origin_area', to='changes.PossibleImplementationArea')), + ('process', models.ForeignKey(null=True, on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, to='asmfa.Process')), + ], + options={ + 'abstract': False, + 'default_permissions': ('add', 'change', 'delete', 'view'), + }, + bases=(repair.apps.login.models.bases.GDSEModelMixin, models.Model), + ), + migrations.AddField( + model_name='solutionpart', + name='flow_changes', + field=models.ForeignKey(null=True, on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, related_name='reference_flow_change', to='changes.FlowReference'), + ), + migrations.AddField( + model_name='solutionpart', + name='flow_reference', + field=models.ForeignKey(null=True, on_delete=repair.apps.utils.protect_cascade.PROTECT_CASCADE, related_name='reference_solution_part', to='changes.FlowReference'), + ), + ] diff --git a/repair/apps/changes/migrations/0046_auto_20190830_1048.py b/repair/apps/changes/migrations/0046_auto_20190830_1048.py new file mode 100644 index 000000000..8da8ac3b0 --- /dev/null +++ b/repair/apps/changes/migrations/0046_auto_20190830_1048.py @@ -0,0 +1,23 @@ +# Generated by Django 2.0 on 2019-08-30 08:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('changes', '0045_auto_20190806_1421'), + ] + + operations = [ + migrations.AddField( + model_name='flowreference', + name='hazardous', + field=models.IntegerField(default=-1), + ), + migrations.AddField( + model_name='flowreference', + name='waste', + field=models.IntegerField(default=-1), + ), + ] diff --git a/repair/apps/changes/migrations/0047_flowreference_include_child_materials.py b/repair/apps/changes/migrations/0047_flowreference_include_child_materials.py new file mode 100644 index 000000000..8a2f18816 --- /dev/null +++ b/repair/apps/changes/migrations/0047_flowreference_include_child_materials.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-27 08:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('changes', '0046_auto_20190830_1048'), + ] + + operations = [ + migrations.AddField( + model_name='flowreference', + name='include_child_materials', + field=models.BooleanField(default=False), + ), + ] diff --git a/repair/apps/changes/models/solutions.py b/repair/apps/changes/models/solutions.py index 5b0834ae7..1676c3fdf 100644 --- a/repair/apps/changes/models/solutions.py +++ b/repair/apps/changes/models/solutions.py @@ -7,6 +7,9 @@ from enumfields import EnumIntegerField from django.contrib.gis.geos import Polygon +from enumfields import EnumIntegerField +from enum import Enum + from repair.apps.login.models import (GDSEUniqueNameModel, GDSEModel) from repair.apps.asmfa.models import (Activity, KeyflowInCasestudy, @@ -20,6 +23,15 @@ _(u'Enter only floats separated by commas.'), 'invalid') +class Scheme(Enum): + MODIFICATION = 0 + NEW = 1 + SHIFTORIGIN = 2 + SHIFTDESTINATION = 3 + PREPEND = 4 + APPEND = 5 + + class SolutionCategory(GDSEModel): name = models.TextField() keyflow = models.ForeignKey(KeyflowInCasestudy, @@ -42,17 +54,6 @@ class Solution(GDSEModel): blank=True) activities_image = models.ImageField(upload_to='charts', null=True, blank=True) - possible_implementation_area = models.MultiPolygonField( - null=True, srid=4326, blank=True) - - @property - def edit_mask(self): - if not self.possible_implementation_area: - return - bbox = Polygon([[-180, 90], [180, 90], - [180, -90], [-180, -90], [-180, 90]]) - mask = bbox.difference(self.possible_implementation_area) - return json.loads(mask.geojson) class ImplementationQuestion(GDSEModel): @@ -72,6 +73,47 @@ class ImplementationQuestion(GDSEModel): is_absolute = models.BooleanField(default=False) +class PossibleImplementationArea(GDSEModel): + ''' + possible implementation with question asked to user + ''' + geom = models.MultiPolygonField(srid=4326) + question = models.TextField(default='') + solution = models.ForeignKey(Solution, on_delete=models.CASCADE, + related_name='possible_implementation_area') + + @property + def edit_mask(self): + if not self.geom: + return + bbox = Polygon([[-180, 90], [180, 90], + [180, -90], [-180, -90], [-180, 90]]) + mask = bbox.difference(self.geom) + return json.loads(mask.geojson) + + +class FlowReference(GDSEModel): + origin_activity = models.ForeignKey( + Activity, on_delete=PROTECT_CASCADE, + related_name='reference_origin', null=True) + destination_activity = models.ForeignKey( + Activity, on_delete=PROTECT_CASCADE, + related_name='reference_destination', null=True) + material = models.ForeignKey( + Material, on_delete=PROTECT_CASCADE, null=True) + include_child_materials = models.BooleanField(default=False) + process = models.ForeignKey( + Process, on_delete=PROTECT_CASCADE, null=True) + waste = models.IntegerField(default=-1) + hazardous = models.IntegerField(default=-1) + origin_area = models.ForeignKey( + PossibleImplementationArea, on_delete=PROTECT_CASCADE, + related_name='possible_origin_area', null=True) + destination_area = models.ForeignKey( + PossibleImplementationArea, on_delete=PROTECT_CASCADE, + related_name='possible_destination_area', null=True) + + class SolutionPart(GDSEModel): ''' part of the solution definition, change a single implementation flow (or @@ -81,39 +123,32 @@ class SolutionPart(GDSEModel): related_name='solution_parts') name = models.TextField() documentation = models.TextField(default='') - implements_new_flow = models.BooleanField(default=False) - references_part = models.BooleanField(default=False) - # starting point of calculation (possible new flow is derived from it) - # on activity level (only when references_part == False) - implementation_flow_origin_activity = models.ForeignKey( - Activity, on_delete=PROTECT_CASCADE, - related_name='implementation_origin', null=True) - implementation_flow_destination_activity = models.ForeignKey( - Activity, on_delete=PROTECT_CASCADE, - related_name='implementation_destination', null=True) - implementation_flow_material = models.ForeignKey( - Material, on_delete=PROTECT_CASCADE, - related_name='implementation_material', null=True) - implementation_flow_process = models.ForeignKey( - Process, on_delete=PROTECT_CASCADE, - related_name='implementation_process', null=True) - - # alternative: derive from another solution part that implements a new flow - # (references_part == True) - implementation_flow_solution_part = models.ForeignKey( - "SolutionPart", on_delete=PROTECT_CASCADE, - related_name='implementation_part', null=True) - - # where is solution part applied (origin, destination or both) - implementation_flow_spatial_application = EnumIntegerField( - enum=SpatialChoice, default=SpatialChoice.BOTH) + # scheme determines how to interpret the following attributes and how + # to apply values to graph/existing flows + scheme = EnumIntegerField(enum=Scheme, default=Scheme.MODIFICATION) + + # implementation flows a.k.a. reference flows + # starting point of calculation, flow changes are referenced to the flows + # filtered by these attributes + flow_reference = models.ForeignKey( + FlowReference, on_delete=PROTECT_CASCADE, null=True, + related_name='reference_solution_part') + # changes made to reference flows + flow_changes = models.ForeignKey( + FlowReference, on_delete=PROTECT_CASCADE, null=True, + related_name='reference_flow_change') + + # order of calculation, lowest first + priority = models.IntegerField(default=0) + ### attributes that determine the new value of the flow ### # question for user inputs for the formula changing the implementation flow # (null when no question is asked) question = models.ForeignKey(ImplementationQuestion, null=True, on_delete=PROTECT_CASCADE) + # only of interest if there is no question (question is null) # is overriden by question.is_absolute is_absolute = models.BooleanField(default=False) @@ -122,25 +157,12 @@ class SolutionPart(GDSEModel): a = models.FloatField() b = models.FloatField() - # material changes (null if stays same) - new_material = models.ForeignKey(Material, null=True, - on_delete=PROTECT_CASCADE) - - # order of calculation, lowest first - priority = models.IntegerField(default=0) - - ### fields only of interest for new flow (implements_new_flow == True) ### - - # origin is kept the same (True) -> new destination - # destination is kept the same (False) -> new origin - keep_origin = models.BooleanField(default=True) - # new origin resp. destination (depending on keep_origin) - # should not be null when implementing new flow - new_target_activity = models.ForeignKey( - Activity, on_delete=PROTECT_CASCADE, - related_name='new_target', null=True) - # text telling user what to pick on map (actor from new_target_activity) - map_request = models.TextField(default='') + def delete(self, **kwargs): + if self.flow_reference: + self.flow_reference.delete() + if self.flow_changes: + self.flow_changes.delete() + super().delete(**kwargs) class AffectedFlow(GDSEModel): @@ -148,16 +170,16 @@ class AffectedFlow(GDSEModel): flow affected by solution-part on activity level ''' solution_part = models.ForeignKey( - SolutionPart, related_name='affected_flow', on_delete=models.CASCADE) + SolutionPart, related_name='affected_flows', on_delete=models.CASCADE) origin_activity = models.ForeignKey( - Activity, on_delete=PROTECT_CASCADE, related_name='affected_origin') + Activity, on_delete=PROTECT_CASCADE, related_name='affected_origins') destination_activity = models.ForeignKey( Activity, on_delete=PROTECT_CASCADE, - related_name='affected_destination') + related_name='affected_destinations') material = models.ForeignKey( - Material, on_delete=PROTECT_CASCADE, related_name='affected_material') + Material, on_delete=PROTECT_CASCADE, related_name='affected_materials') process = models.ForeignKey( - Process, on_delete=PROTECT_CASCADE, related_name='affected_process', + Process, on_delete=PROTECT_CASCADE, related_name='affected_processes', null=True) diff --git a/repair/apps/changes/models/strategies.py b/repair/apps/changes/models/strategies.py index f2b269258..1cb7fe98b 100644 --- a/repair/apps/changes/models/strategies.py +++ b/repair/apps/changes/models/strategies.py @@ -6,9 +6,8 @@ from repair.apps.asmfa.models import KeyflowInCasestudy, Actor from repair.apps.studyarea.models import Stakeholder -from repair.apps.changes.models.solutions import (Solution, - SolutionPart, - ImplementationQuestion) +from repair.apps.changes.models.solutions import ( + Solution, SolutionPart, ImplementationQuestion, PossibleImplementationArea) from repair.apps.utils.protect_cascade import PROTECT_CASCADE @@ -42,8 +41,6 @@ class SolutionInStrategy(GDSEModel): related_name='solutioninstrategy') participants = models.ManyToManyField(Stakeholder) note = models.TextField(blank=True, null=True) - geom = models.GeometryCollectionField(verbose_name='geom', srid=4326, - null=True) # order of calculation, lowest first priority = models.IntegerField(default=0) @@ -53,15 +50,6 @@ def __str__(self): return text.format(s=self.solution, i=self.strategy,) -class ActorInSolutionPart(GDSEModel): - solutionpart = models.ForeignKey(SolutionPart, on_delete=PROTECT_CASCADE, - related_name='targetactor') - actor = models.ForeignKey(Actor, on_delete=PROTECT_CASCADE) - implementation = models.ForeignKey(SolutionInStrategy, - on_delete=models.CASCADE, - related_name='picked_actors') - - class ImplementationQuantity(GDSEModel): ''' answer by user to a implementation question @@ -74,6 +62,18 @@ class ImplementationQuantity(GDSEModel): value = models.FloatField() +class ImplementationArea(GDSEModel): + ''' + answer by user to a implementation question + ''' + implementation = models.ForeignKey(SolutionInStrategy, + on_delete=models.CASCADE, + related_name='implementation_area') + possible_implementation_area = models.ForeignKey( + PossibleImplementationArea, on_delete=models.CASCADE) + geom = models.MultiPolygonField(null=True, srid=4326, blank=True) + + def trigger_implementationquantity_sii(sender, instance, created, **kwargs): """ @@ -85,8 +85,23 @@ def trigger_implementationquantity_sii(sender, instance, solution = Solution.objects.get(pk=sii.solution.id) for question in solution.question.all(): new, is_created = ImplementationQuantity.objects.\ - get_or_create(implementation=sii, question=question, - value=9958.0) + get_or_create(implementation=sii, question=question, value=0) + if is_created: + new.save() + +def trigger_implementationarea_sii(sender, instance, + created, **kwargs): + """ + Create ImplementationArea for each PossibleImplementationArea + each time a SolutionInStrategy is created. + """ + if created: + sii = instance + solution = Solution.objects.get(pk=sii.solution.id) + for area in solution.possible_implementation_area.all(): + new, is_created = ImplementationArea.objects.\ + get_or_create(implementation=sii, + possible_implementation_area=area) if is_created: new.save() @@ -96,3 +111,9 @@ def trigger_implementationquantity_sii(sender, instance, weak=False, dispatch_uid='models.trigger_implementationquantity_sii') +signals.post_save.connect( + trigger_implementationarea_sii, + sender=SolutionInStrategy, + weak=False, + dispatch_uid='models.trigger_implementationarea_sii') + diff --git a/repair/apps/changes/serializers/solutions.py b/repair/apps/changes/serializers/solutions.py index 336a069d1..c09aed611 100644 --- a/repair/apps/changes/serializers/solutions.py +++ b/repair/apps/changes/serializers/solutions.py @@ -1,12 +1,21 @@ from rest_framework import serializers from rest_framework_nested.serializers import NestedHyperlinkedModelSerializer +from django.contrib.gis.db.models.functions import MakeValid, GeoFunc +from django.core.exceptions import ValidationError +from django.db.models import Q +from django.utils.translation import gettext as _ -from repair.apps.asmfa.models import Activity +from repair.apps.asmfa.models import Activity, Actor from repair.apps.changes.models import (SolutionCategory, Solution, ImplementationQuestion, + SolutionInStrategy, + ImplementationQuantity, SolutionPart, - AffectedFlow + AffectedFlow, + PossibleImplementationArea, + Scheme, + FlowReference ) from repair.apps.login.serializers import (InCasestudyField, @@ -19,6 +28,10 @@ from repair.apps.utils.serializers import EnumField +class CollectionExtract(GeoFunc): + function='ST_CollectionExtract' + + class SolutionCategorySerializer(CreateWithUserInCasestudyMixin, NestedHyperlinkedModelSerializer): parent_lookup_kwargs = { @@ -62,6 +75,65 @@ class Meta: 'select_values': {'required': False}} +class PossibleImplementationAreaSerializer(SolutionDetailCreateMixin, + NestedHyperlinkedModelSerializer): + solution = IDRelatedField(read_only=True) + edit_mask = serializers.ReadOnlyField() + affected_actors = serializers.SerializerMethodField() + parent_lookup_kwargs = { + 'casestudy_pk': 'solution__solution_category__keyflow__casestudy__id', + 'keyflow_pk': 'solution__solution_category__keyflow__id', + 'solution_pk': 'solution__id' + } + + class Meta: + model = PossibleImplementationArea + fields = ('url', 'id', 'solution', 'question', 'geom', 'edit_mask', + 'affected_actors') + + def create(self, validated_data): + instance = super().create(validated_data) + return self.makevalid(instance) + + def update(self, instance, validated_data): + instance = super().update(instance, validated_data) + return self.makevalid(instance) + + def makevalid(self, instance): + if not instance.geom.valid: + qs = PossibleImplementationArea.objects.filter(id=instance.id) + qs.update(geom=CollectionExtract(MakeValid('geom'), 3)) + instance = qs[0] + return instance + + def get_affected_actors(self, obj): + parts = obj.solution.solution_parts + #references = parts.values_list('flow_reference', flat=True) + #changes = parts.values_list('flow_changes', flat=True) + #flow_references = FlowReference.objects.filter( + #id__in=list(references) + list(changes)) + #flow_references.filter(Q(origin_area=obj) | Q(destination_area=obj)) + + # should be a sufficient filter because the area is solution specific + flow_references = FlowReference.objects.filter( + Q(origin_area=obj) | Q(destination_area=obj)) + affected_actors = [] + for flow_reference in flow_references: + for area, activity in [ + (flow_reference.origin_area, + flow_reference.origin_activity), + (flow_reference.destination_area, + flow_reference.destination_activity)]: + if area != obj: + continue + actors = Actor.objects.filter(activity=activity) + actors = actors.filter( + administrative_location__geom__intersects=obj.geom) + affected_actors.extend(actors.values_list('id', flat=True)) + affected_actors = set(affected_actors) + return affected_actors + + class SolutionSerializer(CreateWithUserInCasestudyMixin, NestedHyperlinkedModelSerializer): parent_lookup_kwargs = { @@ -73,7 +145,6 @@ class SolutionSerializer(CreateWithUserInCasestudyMixin, currentstate_image = serializers.ImageField(required=False, allow_null=True) activities_image = serializers.ImageField(required=False, allow_null=True) effect_image = serializers.ImageField(required=False, allow_null=True) - edit_mask = serializers.ReadOnlyField() implementation_count = serializers.SerializerMethodField() affected_activities = serializers.SerializerMethodField() @@ -83,16 +154,11 @@ class Meta: 'documentation', 'solution_category', 'activities_image', 'currentstate_image', 'effect_image', - 'possible_implementation_area', - 'edit_mask', 'implementation_count', + 'implementation_count', 'affected_activities' ) read_only_fields = ('url', 'id', ) extra_kwargs = { - 'possible_implementation_area': { - 'allow_null': True, - 'required': False, - }, 'description': {'required': False}, 'documentation': {'required': False}, } @@ -103,11 +169,12 @@ def get_implementation_count(self, obj): def get_affected_activities(self, obj): parts = SolutionPart.objects.filter(solution=obj) activities = parts.values_list( - 'implementation_flow_origin_activity__id', - 'implementation_flow_destination_activity__id', - 'new_target_activity__id', - 'affected_flow__destination_activity__id', - 'affected_flow__origin_activity__id' + 'flow_reference__origin_activity__id', + 'flow_reference__destination_activity__id', + 'flow_changes__origin_activity__id', + 'flow_changes__destination_activity__id', + 'affected_flows__destination_activity__id', + 'affected_flows__origin_activity__id' ) activities = set([i for s in activities for i in s]) try: @@ -129,72 +196,187 @@ class Meta: } -class SolutionPartSerializer(CreateWithUserInCasestudyMixin, - NestedHyperlinkedModelSerializer): +class FlowReferenceSerializer(serializers.ModelSerializer): + + origin_activity = IDRelatedField( + required=False, allow_null=True) + destination_activity = IDRelatedField( + required=False, allow_null=True) + material = IDRelatedField( + required=False, allow_null=True) + process = IDRelatedField( + required=False, allow_null=True) + origin_area = IDRelatedField( + required=False, allow_null=True) + destination_area = IDRelatedField( + required=False, allow_null=True) + + class Meta: + model = FlowReference + fields = ('origin_activity', 'destination_activity', + 'material', 'include_child_materials', 'process', + 'origin_area', 'destination_area', 'waste', 'hazardous') + extra_kwargs = { + 'waste': {'required': False}, + 'hazardous': {'required': False} + } + + +class SolutionPartSerializer(serializers.ModelSerializer): solution = IDRelatedField(read_only=True) parent_lookup_kwargs = { 'casestudy_pk': 'solution__solution_category__keyflow__casestudy__id', 'keyflow_pk': 'solution__solution_category__keyflow__id', 'solution_pk': 'solution__id' } - implementation_flow_origin_activity = IDRelatedField( - required=False, allow_null=True) - implementation_flow_destination_activity = IDRelatedField( - required=False, allow_null=True) - implementation_flow_material = IDRelatedField( - required=False, allow_null=True) - implementation_flow_solution_part = IDRelatedField( - required=False, allow_null=True) - new_material = IDRelatedField(required=False, allow_null=True) - new_target_activity = IDRelatedField(required=False, allow_null=True) - implementation_flow_spatial_application = EnumField(enum=SpatialChoice) - affected_flows = AffectedFlowSerializer(source='affected_flow', many=True) + scheme = EnumField(enum=Scheme) + flow_reference = FlowReferenceSerializer(allow_null=True) + flow_changes = FlowReferenceSerializer(allow_null=True, required=False) + + affected_flows = AffectedFlowSerializer(many=True) question = IDRelatedField(allow_null=True) # ToDo: serialize affected flows as part of this serializer class Meta: model = SolutionPart - fields = ('url', 'id', 'name', 'solution', 'documentation', - 'implements_new_flow', 'references_part', - 'implementation_flow_origin_activity', - 'implementation_flow_destination_activity', - 'implementation_flow_material', - 'implementation_flow_process', - 'implementation_flow_spatial_application', - 'implementation_flow_solution_part', - 'new_material', + fields = ('id', 'name', 'solution', + 'scheme', 'documentation', + 'flow_reference', + 'flow_changes', 'question', 'a', 'b', - 'keep_origin', 'new_target_activity', - 'map_request', 'priority', + 'priority', 'affected_flows', 'is_absolute', ) read_only_fields = ('url', 'id', 'solution') extra_kwargs = { - 'implementation_question': {'null': True, 'required': False}, - 'keep_origin': {'required': False}, - 'map_request': {'required': False}, 'documentation': {'required': False, 'allow_blank': True}, - 'map_request': {'required': False, 'allow_blank': True}, 'is_absolute': {'required': False} } + depending_requirements = { + 'scheme': { + 'NEW': [ + 'flow_changes__origin_activity', + 'flow_changes__destination_activity', + 'flow_changes__material' + ], + 'MODIFICATION': [ + 'flow_reference__origin_activity', + 'flow_reference__destination_activity', + 'flow_reference__material' + ], + 'SHIFTDESTINATION': [ + 'flow_reference__origin_activity', + 'flow_reference__destination_activity', + 'flow_reference__material', + 'flow_changes__destination_activity' + ], + 'SHIFTORIGIN': [ + 'flow_reference__origin_activity', + 'flow_reference__destination_activity', + 'flow_reference__material', + 'flow_changes__origin_activity' + ], + 'PREPEND': [ + 'flow_reference__origin_activity', + 'flow_reference__destination_activity', + 'flow_reference__material', + 'flow_changes__origin_activity' + ], + 'APPEND': [ + 'flow_reference__origin_activity', + 'flow_reference__destination_activity', + 'flow_reference__material', + 'flow_changes__destination_activity' + ], + } + } + + def validate(self, data): + ''' + check fields that are not defined as required but whose requirements + follows an internal logic depending on scheme + ''' + request = self.context['request'] + + # patching single attributes is going unchecked + # ToDo: patching might mess up the logic + if request.method == 'PATCH' and 'scheme' not in data: + return data + + scheme = data['scheme'].name + required = self.Meta.depending_requirements['scheme'][scheme] + errors = {} + # ToDo: return different message if field is not in data + error_msg = _('This field may not be blank.') + for required_field in required: + subfield = None + if '__' in required_field: + required_field, subfield = required_field.split('__') + value = data.get(required_field, None) + if not value: + errors[required_field] = error_msg + if subfield: + subvalue = value.get(subfield, '') + if not subvalue: + errors[f'{required_field}__{subfield}'] = error_msg + question = data.get('question', None) + if not question and 'is_absolute' not in data: + errors['is_absolute'] = error_msg + if len(errors) > 0: + raise ValidationError(errors) + return data + + def create(self, validated_data): + v = validated_data.copy() + v.pop('affected_flows', None) + v.pop('flow_reference', None) + v.pop('flow_changes', None) + instance = super().create(v) + return self.update(instance, validated_data) def update(self, instance, validated_data): - new_flows = validated_data.pop('affected_flow', None) + affected_flows = validated_data.pop('affected_flows', None) + flow_reference = validated_data.pop('flow_reference', None) + flow_changes = validated_data.pop('flow_changes', None) + question = validated_data.get('question', None) instance = super().update(instance, validated_data) - if new_flows: + if flow_reference: + if instance.flow_reference: + instance.flow_reference.delete() + ref_model = FlowReference(**flow_reference) + ref_model.save() + instance.flow_reference = ref_model + if flow_changes: + if instance.flow_changes: + instance.flow_changes.delete() + ref_model = FlowReference(**flow_changes) + ref_model.save() + instance.flow_changes = ref_model + if affected_flows: AffectedFlow.objects.filter(solution_part=instance).delete() - for f in new_flows: + for f in affected_flows: flow = AffectedFlow(solution_part=instance, **f) flow.save() - if instance.references_part: - instance.implementation_flow_origin_activity = None - instance.implementation_flow_destination_activity = None - instance.implementation_flow_material = None - instance.implementation_flow_process = None - else: - instance.implementation_flow_solution_part = None + if question: + implementations = SolutionInStrategy.objects.filter( + solution=instance.solution) + for implementation in implementations: + ex = ImplementationQuantity.objects.filter( + implementation=implementation, question=question) + # create if not existing (in case questions were added after + # creation the implementation by user) + if len(ex) != 1: + # workaround: old code produced duplicate quantities, + # remove duplicates + ex.delete() + new = ImplementationQuantity( + implementation=implementation, + question=question, + value=0 + ) + new.save() instance.save() return instance diff --git a/repair/apps/changes/serializers/strategies.py b/repair/apps/changes/serializers/strategies.py index a84f26a99..bef6fb64e 100644 --- a/repair/apps/changes/serializers/strategies.py +++ b/repair/apps/changes/serializers/strategies.py @@ -9,7 +9,7 @@ from repair.apps.changes.models import (Strategy, SolutionInStrategy, ImplementationQuantity, - ActorInSolutionPart, + ImplementationArea ) from repair.apps.login.serializers import (InCasestudyField, @@ -178,10 +178,10 @@ class Meta: fields = ('question', 'value') -class ActorInSolutionPartSerializer(serializers.ModelSerializer): +class ImplementationAreaSerializer(serializers.ModelSerializer): class Meta: - model = ActorInSolutionPart - fields = ('solutionpart', 'actor') + model = ImplementationArea + fields = ('possible_implementation_area', 'geom') class SolutionInStrategySerializer(serializers.ModelSerializer): @@ -193,16 +193,17 @@ class SolutionInStrategySerializer(serializers.ModelSerializer): participants = IDRelatedField(many=True, required=False) quantities = ImplementationQuantitySerializer( many=True, source='implementation_quantity', required=False) - picked_actors = ActorInSolutionPartSerializer(many=True, required=False) + areas = ImplementationAreaSerializer( + many=True, source='implementation_area', required=False) class Meta: model = SolutionInStrategy - fields = ('id', 'solution', 'note', 'geom', - 'participants', 'priority', 'quantities', 'picked_actors') + fields = ('id', 'solution', 'note', + 'participants', 'priority', 'quantities', 'areas') def update(self, instance, validated_data): quantities = validated_data.pop('implementation_quantity', []) - picked_actors = validated_data.pop('picked_actors', None) + areas = validated_data.pop('implementation_area', []) instance = super().update(instance, validated_data) for q in quantities: # quantities are created automatically, no need to delete them @@ -210,10 +211,12 @@ def update(self, instance, validated_data): question=q['question'], implementation=instance) quantity.value = q['value']; quantity.save() - if picked_actors: - ActorInSolutionPart.objects.filter(implementation=instance).delete() - for a in picked_actors: - ais = ActorInSolutionPart(implementation=instance, **a) - ais.save() + for a in areas: + # quantities are created automatically, no need to delete them + area = ImplementationArea.objects.get_or_create( + possible_implementation_area=a['possible_implementation_area'], + implementation=instance)[0] + area.geom = a['geom'] + area.save() return instance diff --git a/repair/apps/changes/tests/__init__.py b/repair/apps/changes/tests/__init__.py index 03319e437..8d1389ce3 100644 --- a/repair/apps/changes/tests/__init__.py +++ b/repair/apps/changes/tests/__init__.py @@ -1,2 +1,3 @@ from .test_solutions import * from .test_strategies import * +from .test_graphwalker import * \ No newline at end of file diff --git a/repair/apps/changes/tests/test_graphwalker.py b/repair/apps/changes/tests/test_graphwalker.py new file mode 100644 index 000000000..f0b2fb710 --- /dev/null +++ b/repair/apps/changes/tests/test_graphwalker.py @@ -0,0 +1,252 @@ +import random +from django.test import TestCase +from django.contrib.gis.geos import Point +from django.db.models import Count, Sum +from repair.apps.asmfa.models import (Actor, Activity, Actor2Actor, + ActorStock, AdministrativeLocation, + FractionFlow, Material) +from repair.apps.changes.models import Solution, Strategy +from repair.apps.asmfa.graphs.graph import StrategyGraph + + +class ClosestActorMixin: + def test_select_closest_actor(self): + max_distance = 10000 + actors_in_solution = Actor.objects.filter(activity__id__lte=1845) + possible_target_actors = Actor.objects.filter(activity__id__gte=1846) + target_actors = StrategyGraph.find_closest_actor( + actors_in_solution, + possible_target_actors, + absolute_max_distance=max_distance, + ) + assert len(target_actors) <= len(actors_in_solution), \ + f'number of target actors found should be less or equal '\ + f'than the actors in solution for which a target actor '\ + f'was searched' + actor_in_solution_set = set( + actors_in_solution.values_list('id', flat=True)) + actors_found = set(target_actors.keys()) + assert actors_found <= actor_in_solution_set, \ + f'actors found_ {actors_found} must be a subset '\ + f'of the actors searched: {actor_in_solution_set}' + + possible_target_set = set( + possible_target_actors.values_list('id', flat=True)) + chosen_target_set = set(target_actors.values()) + assert possible_target_set >= chosen_target_set, \ + f'chosen targets {chosen_target_set} has to be a subset of '\ + f'possible target actors {possible_target_set}' + + # test maximum distance for all actor pairs + + for actor_id1, actor_id2 in target_actors.items(): + actor1 = Actor.objects.get(id=actor_id1) + actor2 = Actor.objects.get(id=actor_id2) + pnt1 = actor1.administrative_location.geom.transform(3035, + clone=True) + pnt2 = actor2.administrative_location.geom.transform(3035, + clone=True) + + assert pnt1.distance(pnt2) <= max_distance, \ + f'distance between actor {actors1[i]} and {actors2[i]} '\ + f'is {pnt1.distance(pnt2)} > {max_distance} (max_distance)' + + +class GraphWalkerTests(TestCase, ClosestActorMixin): + """ + Testclass for the graph walker + + loads the data for the peel pioneer example from a fixture + """ + + fixtures = ['peelpioneer_data'] + + def test_solution_logic(self): + # this solution is not in the fixtures anymore + pass + #solutions = Solution.objects.all() + #assert len(solutions) == 1 + #solution = Solution.objects.get(pk=89) + #assert solution.name == "Simplified Peel Pioneer" + + +class MultiplyTestDataMixin: + + @classmethod + def setUpTestData(cls): + """multiply the test data for the peelpioneer_data""" + n_clones = 5 + activities = Activity.objects.all() + + # clone actors + new_actors = [] + new_stocks = [] + new_fraction_flows = [] + new_locations = [] + for activity in activities: + actors = Actor.objects.filter(activity=activity) + for actor in actors: + for i in range(n_clones): + new_actor = Actor( + activity=actor.activity, + BvDid=actor.BvDid, + name=f'{actor.name}_{i}', + reason=actor.reason, + included=actor.included, + consCode=actor.consCode, + year=actor.year, + BvDii=actor.BvDii, + employees=actor.employees, + turnover=actor.turnover, + ) + new_actors.append(new_actor) + new_actor.save() + + # add new stocks + try: + old_stock = ActorStock.objects.get(origin=actor) + new_amount = (random.random() + 0.5) * old_stock.amount + new_stock = ActorStock( + origin=new_actor, + publication=old_stock.publication, + composition=old_stock.composition, + amount=new_amount, + keyflow=old_stock.keyflow, + year=old_stock.year, + waste=old_stock.Actor2Actor.objectswaste, + ) + new_stocks.append(new_stock) + + # add the fractionflow for the stocks + old_fraction_flow = FractionFlow.objects.get( + stock=old_stock) + + new_fraction_flow = FractionFlow( + stock=new_stock, + origin=new_actor, + material=old_fraction_flow.material, + to_stock=True, + amount=new_amount, + publication=old_fraction_flow.publication, + avoidable=old_fraction_flow.avoidable, + hazardous=old_fraction_flow.hazardous, + nace=old_fraction_flow.nace, + composition_name=old_fraction_flow.composition_name, + strategy=old_fraction_flow.strategy, + ) + new_fraction_flows.append(new_fraction_flow) + + except (ActorStock.DoesNotExist, + FractionFlow.DoesNotExist): + # continue if no stock or fraction flow exists + pass + + # add new locations + old_location = AdministrativeLocation.objects.filter( + actor=actor) + if old_location: + old_location = old_location[0] + # spatial offset by +/- 0.01 degrees + dx = random.randint(-100, 100) / 10000. + dy = random.randint(-100, 100) / 10000. + geom = Point(x=old_location.geom.x + dx, + y=old_location.geom.y + dy, + srid=old_location.geom.srid) + new_location = AdministrativeLocation( + actor=new_actor, + area=old_location.area, + geom=geom) + new_locations.append(new_location) + + #Actor.objects.bulk_create(new_actors) + ActorStock.objects.bulk_create(new_stocks) + AdministrativeLocation.objects.bulk_create(new_locations) + + #clone flows + new_flows = [] + + # get the different combinations of origin and destination activities + origins_destinations = Actor2Actor.objects\ + .values('origin__activity', + 'destination__activity')\ + .annotate(Count('id')) + + for origin_destination in origins_destinations: + activity1 = Activity.objects.get( + id=origin_destination['origin__activity']) + activity2 = Activity.objects.get( + id=origin_destination['destination__activity']) + flows = Actor2Actor.objects.filter(origin__activity=activity1, + destination__activity=activity2) + fraction_flows = FractionFlow.objects.filter( + origin__activity=activity1, + destination__activity=activity2) + fraction_flow_materials = fraction_flows\ + .values('material')\ + .annotate(Count('id'))\ + .annotate(Sum('amount')) + total_amount = fraction_flows.aggregate(Sum('amount'))\ + ['amount__sum'] + + source_actors = Actor.objects.filter(activity=activity1) + destination_actors = Actor.objects.filter(activity=activity2) + for flow in flows: + for i in range(n_clones): + new_amount = (random.random() + 0.5) * flow.amount + new_flow = Actor2Actor( + origin=random.choice(source_actors), + destination=random.choice(destination_actors), + composition=flow.composition, + publication=flow.publication, + amount=new_amount, + waste=flow.waste, + process=flow.process, + keyflow=flow.keyflow, + year=flow.year, + ) + new_flows.append(new_flow) + + for fraction_flow_material in fraction_flow_materials: + material = Material.objects.get( + id=fraction_flow_material['material']) + fraction = (fraction_flow_material['amount__sum'] / + total_amount) + + new_fraction_flow = FractionFlow( + keyflow=new_flow.keyflow, + waste=new_flow.waste, + process=new_flow.process, + flow=new_flow, + origin=new_flow.origin, + destination=new_flow.destination, + material=material, + amount=new_amount * fraction, + publication=new_flow.publication, + ) + new_fraction_flows.append(new_fraction_flow) + + Actor2Actor.objects.bulk_create(new_flows) + FractionFlow.objects.bulk_create(new_fraction_flows) + + +class GraphWalkerPerformanceTests(MultiplyTestDataMixin, + TestCase, + ClosestActorMixin): + """ + Testclass for performance tests of the graph walker + + loads the data for the peel pioneer example from a fixture + and clone the data to get a big testcase + """ + + fixtures = ['peelpioneer_data'] + + def test_cloned_actors_and_flows(self): + # this solution is not in the fixtures anymore + print(len(Actor.objects.all())) + print(len(ActorStock.objects.all())) + print(len(AdministrativeLocation.objects.all())) + print(len(Actor2Actor.objects.all())) + print(len(FractionFlow.objects.all())) + + diff --git a/repair/apps/changes/tests/test_strategies.py b/repair/apps/changes/tests/test_strategies.py index 73c04b9ba..25028cbd3 100644 --- a/repair/apps/changes/tests/test_strategies.py +++ b/repair/apps/changes/tests/test_strategies.py @@ -6,13 +6,16 @@ from repair.tests.test import BasicModelPermissionTest, BasicModelReadTest from repair.apps.changes.models import (ImplementationQuantity, SolutionPart, - ActorInSolutionPart, AffectedFlow) + AffectedFlow, Scheme, + ImplementationArea) from repair.apps.asmfa.models import Actor, Activity, Material -from django.contrib.gis.geos import Polygon, Point, GeometryCollection +from django.contrib.gis.geos import Polygon, Point, MultiPolygon from repair.apps.changes.factories import ( SolutionFactory, StrategyFactory, ImplementationQuestionFactory, - SolutionPartFactory, SolutionInStrategyFactory + SolutionPartFactory, SolutionInStrategyFactory, + FlowReferenceFactory, PossibleImplementationAreaFactory, + ImplementationAreaFactory ) from repair.apps.asmfa.factories import ( ActivityFactory, ActivityGroupFactory, ActorFactory, MaterialFactory, @@ -47,6 +50,20 @@ def setUpClass(cls): solution=cls.solution ) + possible_implementation_area_1 = PossibleImplementationAreaFactory( + solution=cls.solution, + geom=MultiPolygon(Polygon(((0.0, 0.0), (0.0, 20.0), (56.0, 20.0), + (56.0, 0.0), (0.0, 0.0)))), + question=("") + ) + + possible_implementation_area_2 = PossibleImplementationAreaFactory( + solution=cls.solution, + geom=MultiPolygon(Polygon(((0.0, 0.0), (0.0, 20.0), (56.0, 20.0), + (56.0, 0.0), (0.0, 0.0)))), + question=("") + ) + part_1 = SolutionPartFactory( solution=cls.solution, question=question_1, @@ -63,13 +80,15 @@ def setUpClass(cls): for i in range(3): ActorFactory(activity=target_activity) + new_target = FlowReferenceFactory( + destination_activity=target_activity, + destination_area=possible_implementation_area_1 + ) + part_new_flow = SolutionPartFactory( solution=cls.solution, question=question_1, - implements_new_flow=True, - keep_origin=True, - new_target_activity=target_activity, - map_request="pick an actor" + flow_changes=new_target ) def test01_quantities(self): @@ -85,25 +104,9 @@ def test01_quantities(self): quantities = ImplementationQuantity.objects.all() assert quantities.count() == 2 - def test02_target_actor(self): - """Test the new solution strategy""" - - strategy = StrategyFactory() - solution_in_strategy = SolutionInStrategyFactory( - solution=self.solution, - strategy=strategy) - - new_flow_parts = SolutionPart.objects.filter( - solution=self.solution, implements_new_flow=True) + impl_areas = ImplementationArea.objects.all() + assert impl_areas.count() == 2 - for part in new_flow_parts: - target = part.new_target_activity - actors = Actor.objects.filter(activity=target) - target_actor = ActorInSolutionPart( - solutionpart=part, actor=actors.first(), - implementation=solution_in_strategy - ) - # ToDo: test sth meaningful here? class BreadToBeerSolution(GenerateBreadToBeerData): """Define the Solution for the Bread to Beer case""" @@ -125,28 +128,32 @@ def setUpClass(cls): ## Solution Parts ## brewing_activity = Activity.objects.filter(name='Brewery', nace='A-0000') household_activity = Activity.objects.filter(name='Household', nace='A-0001') - incinerator_activity = Activity.objects.filter(name='Incineration', nace='C-0001') - farming_activity = Activity.objects.filter(name='Farming', nace='C-0000') + incinerator_activity = Activity.objects.filter(name='Incineration', nace='C-0011') + farming_activity = Activity.objects.filter(name='Farming', nace='C-0010') bread = Material.objects.filter(name='bread', keyflow=cls.keyflow) barley = Material.objects.filter(name='barley', keyflow=cls.keyflow) + implementation = FlowReferenceFactory( + origin_activity=household_activity[0], + destination_activity=incinerator_activity[0], + material=bread[0] + ) + + shift = FlowReferenceFactory( + destination_activity=brewing_activity[0] + ) + cls.bread_to_brewery = SolutionPartFactory( solution=cls.solution, documentation='Bread goes to Brewery instead of Incineration', question=cls.beer_question, - implements_new_flow=True, - implementation_flow_origin_activity = household_activity[0], - implementation_flow_destination_activity = incinerator_activity[0], - implementation_flow_material = bread[0], + flow_reference=implementation, + flow_changes=shift, + scheme=Scheme.SHIFTDESTINATION, a = 1, b = 0, - keep_origin = True, - new_target_activity = brewing_activity[0], - - map_request = 'Pick a brewery which will process the waste bread', - priority=1 ) @@ -156,34 +163,23 @@ def setUpClass(cls): material=barley[0], solution_part=cls.bread_to_brewery) + class BreadToBeerSolutionTest(BreadToBeerSolution): """Test the Solution definition for the Bread to Beer case""" - def test_setup(self): - assert self.bread_to_brewery.new_target_activity.name == 'Brewery' - assert self.bread_to_brewery.new_target_activity.nace == 'A-0000' - - def test_01_implementation(self): - ## implement the solution as the user would ## - implementation_area = Polygon(((0.0, 0.0), (0.0, 20.0), (56.0, 20.0), - (56.0, 0.0), (0.0, 0.0))) - user = UserInCasestudyFactory(casestudy=self.keyflow.casestudy, user__user__username='Hans Norbert') strategy = StrategyFactory(keyflow=self.keyflow, user=user) implementation = SolutionInStrategyFactory( - solution=self.solution, strategy=strategy, - geom=GeometryCollection(implementation_area)) + solution=self.solution, strategy=strategy) - ActorInSolutionPart(solutionpart=self.bread_to_brewery, - actor=self.brewery_1, - implementation=implementation) answer = ImplementationQuantity(question=self.beer_question, implementation=implementation, value=1) + class ApplyStrategyTest(TestCase): @classmethod @@ -359,153 +355,136 @@ def setUpClass(cls): is_absolute=True # Note CF: is it? ) + cls.possible_implementation_area = PossibleImplementationAreaFactory( + solution=cls.solution, + geom=MultiPolygon(Polygon(((0.0, 0.0), (0.0, 20.0), (56.0, 20.0), + (56.0, 0.0), (0.0, 0.0)))), + question=("Where are the facilities for composting " + "the fungus located?") + ) + ## solution parts ## # the new flow based on flows from c-2399 to f-4110 # Note CF: optional it could also be derived from flows out of the farms - cls.new_fungus_insulation = SolutionPartFactory( - solution=cls.solution, - question=cls.fungus_question, - implements_new_flow = True, - implementation_flow_origin_activity = manufacture_activity, - implementation_flow_destination_activity = building_activity, - implementation_flow_material = wool, - implementation_flow_process = dummy_process, - # Note CF: where does it apply? actually it would make more sense to - # let the user decide about the households (not possible this way) - implementation_flow_spatial_application = SpatialChoice.DESTINATION, - - a = 1, - b = 0, - keep_origin = False, - new_target_activity = growing_activity, - # Note CF: does picking even make sense? why not take all actors from - # new activity??? (only affects flows with fungus in it anyway) - map_request = 'Pick a fungus farm that produces the ', - # Note CF: no possibility to define that the material changed from - # wool to fungus!!!! + implementation = FlowReferenceFactory( + origin_activity=manufacture_activity, + destination_activity=building_activity, + material=wool + ) - priority=1 + shift = FlowReferenceFactory( + origin_activity=growing_activity, + material=fungus ) - # you have to subtract the derived amounts from the original one - # (Note CF: at least i guess you have to, - # would be easier to already mark this in the part defining the new - # flow, this is almost the same definition) - reduction_existing_flow = SolutionPartFactory( + cls.new_fungus_insulation = SolutionPartFactory( solution=cls.solution, question=cls.fungus_question, - implements_new_flow = False, - implementation_flow_origin_activity = manufacture_activity, - implementation_flow_destination_activity = building_activity, - implementation_flow_material = wool, - implementation_flow_process = dummy_process, - implementation_flow_spatial_application = SpatialChoice.DESTINATION, - - a = -1, + flow_reference=implementation, + flow_changes=shift, + scheme=Scheme.SHIFTORIGIN, + a = 1, b = 0, - - priority=2 + priority=1 ) # the new flow based on flows from c-2399 to f-4110 # Note CF: optional it could also be derived from flows out of the farms + + implementation = FlowReferenceFactory( + origin_activity=consumption_activity, + destination_activity=None, + process=dummy_process, + material=wool + ) + + # origin actually stays the same but there is no way to shift without + # referring to either new destination or origin + shift = FlowReferenceFactory( + origin_activity=consumption_activity, + material=fungus + ) + new_fungus_stock = SolutionPartFactory( solution=cls.solution, question=cls.fungus_question, - implements_new_flow = True, - implementation_flow_origin_activity = consumption_activity, - # Note CF: is this enough to tell that the implementation flow - # is a stock? - implementation_flow_destination_activity = None, - implementation_flow_material = wool, - implementation_flow_process = dummy_process, - implementation_flow_spatial_application = SpatialChoice.ORIGIN, - + flow_reference=implementation, + flow_changes=shift, + scheme=Scheme.SHIFTORIGIN, a = 1, b = 0, + priority=2 + ) - keep_origin = True, - - # Note CF: there is no new origin, stays same - new_target_activity = None, - # Note CF: there is nothing to pick, bool for picking or not or nullable? - map_request = '', - # Note CF: again: no possibility to define new material + # new flow from F-4110 development to E-3821 treatment + # Note CF: deriving it from existing F4110 to E3821, just guessing - priority=3 + implementation = FlowReferenceFactory( + origin_activity=building_activity, + destination_activity=treatment_activity, + material=wool, + process=dummy_process ) - # Note CF: do we have to define the reduction of the existing wool - # stock? (same as reduction_existing_flow) + # actually both activities stay the same + shift = FlowReferenceFactory( + origin_activity=building_activity, + destination_activity=treatment_activity, + material=fungus, + process=compost, + destination_area=cls.possible_implementation_area + ) - # new flow from F-4110 development to E-3821 treatment - # Note CF: deriving it from existing F4110 to E3821, just guessing cls.new_building_disposal = SolutionPartFactory( solution=cls.solution, # Note CF: i guess we need different numbers than asked for in this # question, or do we even need a question?? question=cls.fungus_question, - implements_new_flow = True, - implementation_flow_origin_activity = building_activity, - implementation_flow_destination_activity = treatment_activity, - implementation_flow_material = wool, - implementation_flow_process = dummy_process, - implementation_flow_spatial_application = SpatialChoice.ORIGIN, - + flow_reference=implementation, + flow_changes=shift, + scheme=Scheme.SHIFTDESTINATION, a = 1, b = 0, + priority=3 + ) - # actually both activities stay the same - keep_origin = True, - - # Note CF: both activities actually stay the same, but a new - # destination has to be picked for composting - new_target_activity = treatment_activity, - map_request = ('Pick a treatment and disposal facility to ' - 'compost the fungus'), + # new flow from fungus farms to E-3821 treatment + # Note CF: most likely this should already be modelled in the status quo + # deriving it from fungus stock - # Note CF: how to mark that the process changes to compost??? + implementation = FlowReferenceFactory( + origin_activity=growing_activity, + destination_activity=None, + process=compost, + material=fungus + ) - priority=4 + # origin actually stays the same but there is no way to shift without + # referring to either new destination or origin + shift = FlowReferenceFactory( + destination_activity=treatment_activity, + destination_area=cls.possible_implementation_area, + material=fungus ) # Note CF: reduce existing implementation flow? - # new flow from fungus farms to E-3821 treatment - # Note CF: most likely this should already be modelled in the status quo - # deriving it from fungus stock new_fungus_disposal = SolutionPartFactory( solution=cls.solution, # Note CF: is there a question??? question=None, - implements_new_flow = True, - implementation_flow_origin_activity = growing_activity, - implementation_flow_destination_activity = None, - implementation_flow_material = fungus, - implementation_flow_process = compost, # Note CF: ?? - implementation_flow_spatial_application = SpatialChoice.DESTINATION, + flow_reference=implementation, + flow_changes=shift, + scheme=Scheme.SHIFTDESTINATION, a = 1, b = 0, - # actually both activities stay the same - keep_origin = True, - - # if it is to be picked, is it the same as in new_building_disposal? - new_target_activity = treatment_activity, - map_request = ('Pick a treatment and disposal facility to ' - 'compost the fungus'), - - # Note CF: how to mark that the process changes to compost??? - - priority=5 + priority=4 ) - # Note CF: reduce stock of fungus? (maybe we don't need previous - # solution part anyway) - ## affected flows ## # Note CF: poor pull leader has to define the affected flows for @@ -513,7 +492,7 @@ def setUpClass(cls): # (marking the implementation flows as well, i guess that doesn't matter) # B: who cares about the pull leader?! - parts = [cls.new_fungus_insulation, reduction_existing_flow, + parts = [cls.new_fungus_insulation, new_fungus_stock, cls.new_building_disposal, new_fungus_disposal] @@ -559,26 +538,18 @@ def test_01_implementation(self): ## implement the solution as a user would ## - implementation_area = Polygon(((0.0, 0.0), (0.0, 20.0), (56.0, 20.0), - (56.0, 0.0), (0.0, 0.0))) - user = UserInCasestudyFactory(casestudy=self.keyflow.casestudy, user__user__username='Hans Herbert') strategy = StrategyFactory(keyflow=self.keyflow, user=user) implementation = SolutionInStrategyFactory( - solution=self.solution, strategy=strategy, - geom=GeometryCollection(implementation_area)) - - # pick a farm (Note CF: necessary?) - # Note: too lazy to make factories for everything, model does the same - ActorInSolutionPart(solutionpart=self.new_fungus_insulation, - actor=self.fungus_farm_1, - implementation=implementation) + solution=self.solution, strategy=strategy) - # pick a treatment facility for the fungus - ActorInSolutionPart(solutionpart=self.new_building_disposal, - actor=self.treatment_compost, - implementation=implementation) + implementation_area = ImplementationAreaFactory( + implementation=implementation, + geom=MultiPolygon(Polygon(((0.0, 0.0), (0.0, 20.0), (56.0, 20.0), + (56.0, 0.0), (0.0, 0.0)))), + possible_implementation_area=self.possible_implementation_area + ) # answer the question # Note CF: the diagram says 250 * 5 cubic meters, don't know what fungus @@ -587,6 +558,8 @@ def test_01_implementation(self): implementation=implementation, value=25) + # ToDo: asserts + class SolutionInStrategyInCasestudyTest(BasicModelPermissionTest, APITestCase): diff --git a/repair/apps/changes/views/solutions.py b/repair/apps/changes/views/solutions.py index 94544beeb..8fc13010c 100644 --- a/repair/apps/changes/views/solutions.py +++ b/repair/apps/changes/views/solutions.py @@ -2,14 +2,16 @@ SolutionCategory, Solution, ImplementationQuestion, - SolutionPart + SolutionPart, + PossibleImplementationArea ) from repair.apps.changes.serializers import ( SolutionSerializer, SolutionCategorySerializer, ImplementationQuestionSerializer, - SolutionPartSerializer + SolutionPartSerializer, + PossibleImplementationAreaSerializer ) from repair.apps.utils.views import (CasestudyViewSetMixin, @@ -37,3 +39,9 @@ class ImplementationQuestionViewSet(CasestudyViewSetMixin, queryset = ImplementationQuestion.objects.all() +class PossibleImplementationAreaViewSet(CasestudyViewSetMixin, + ModelPermissionViewSet): + serializer_class = PossibleImplementationAreaSerializer + queryset = PossibleImplementationArea.objects.all() + + diff --git a/repair/apps/conclusions/migrations/0003_auto_20190829_1342.py b/repair/apps/conclusions/migrations/0003_auto_20190829_1342.py new file mode 100644 index 000000000..88e613cbb --- /dev/null +++ b/repair/apps/conclusions/migrations/0003_auto_20190829_1342.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-08-29 11:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('conclusions', '0002_auto_20181214_1338'), + ] + + operations = [ + migrations.AlterField( + model_name='conclusion', + name='step', + field=models.CharField(default='None', max_length=20), + ), + ] diff --git a/repair/apps/conclusions/models.py b/repair/apps/conclusions/models.py index 499123073..c1e4bb18c 100644 --- a/repair/apps/conclusions/models.py +++ b/repair/apps/conclusions/models.py @@ -6,16 +6,6 @@ from repair.apps.login.models import CaseStudy -STEP_CHOICES = ( - (0, 'None'), - (1, 'Objectives'), - (2, 'Flow Targets'), - (3, 'Strategies'), - (4, 'Modified Flows'), - (5, 'Sustainability') -) - - class ConsensusLevel(GDSEModel): casestudy = models.ForeignKey(CaseStudy, on_delete=models.CASCADE) name = models.TextField() @@ -36,4 +26,4 @@ class Conclusion(GDSEModel): consensus_level = models.ForeignKey(ConsensusLevel, on_delete=PROTECT_CASCADE) section = models.ForeignKey(Section, on_delete=PROTECT_CASCADE) - step = models.IntegerField(choices=STEP_CHOICES, default=0) + step = models.CharField(max_length=20, default='None') diff --git a/repair/apps/login/serializers/bases.py b/repair/apps/login/serializers/bases.py index 09f2769db..718d36c33 100644 --- a/repair/apps/login/serializers/bases.py +++ b/repair/apps/login/serializers/bases.py @@ -44,8 +44,12 @@ class IDRelatedField(serializers.PrimaryKeyRelatedField): and return all data from this model as a queryset """ def get_queryset(self): - view = self.root.context.get('view') - Model = view.queryset.model + parent = self.parent + if hasattr(self.parent, 'Meta'): + Model = self.parent.Meta.model + else: + view = self.root.context.get('view') + Model = view.queryset.model # look up self.parent in the values of the dictionary self.root.fields # and return the key as the field_name field_name = self.source or self.get_field_name() diff --git a/repair/apps/statusquo/migrations/0018_auto_20190923_1316.py b/repair/apps/statusquo/migrations/0018_auto_20190923_1316.py new file mode 100644 index 000000000..5d950895b --- /dev/null +++ b/repair/apps/statusquo/migrations/0018_auto_20190923_1316.py @@ -0,0 +1,40 @@ +# Generated by Django 2.2.4 on 2019-09-23 13:16 + +import django.core.validators +from django.db import migrations, models +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('statusquo', '0017_flowfilter_anonymize'), + ] + + operations = [ + migrations.AlterField( + model_name='flowfilter', + name='node_ids', + field=models.TextField(blank=True, null=True, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')]), + ), + migrations.AlterField( + model_name='flowfilter', + name='process_ids', + field=models.TextField(blank=True, null=True, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')]), + ), + migrations.AlterField( + model_name='indicatorflow', + name='destination_node_ids', + field=models.TextField(blank=True, null=True, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')]), + ), + migrations.AlterField( + model_name='indicatorflow', + name='origin_node_ids', + field=models.TextField(blank=True, null=True, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')]), + ), + migrations.AlterField( + model_name='indicatorflow', + name='process_ids', + field=models.TextField(blank=True, null=True, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')]), + ), + ] diff --git a/repair/apps/statusquo/models/filters.py b/repair/apps/statusquo/models/filters.py index 80f3c6a65..d64aa7c2a 100644 --- a/repair/apps/statusquo/models/filters.py +++ b/repair/apps/statusquo/models/filters.py @@ -34,9 +34,9 @@ class FlowFilter(GDSEModel): enum=Direction, default=Direction.BOTH) flow_type = EnumIntegerField( enum=FlowType, default=FlowType.BOTH) - process_ids = models.CharField( + process_ids = models.TextField( validators=[validate_comma_separated_integer_list], - blank=True, null=True, max_length=100) + blank=True, null=True) hazardous = EnumIntegerField( enum=TriState, default=TriState.BOTH) avoidable = EnumIntegerField( diff --git a/repair/apps/statusquo/models/indicators.py b/repair/apps/statusquo/models/indicators.py index a30795096..9c198f8c9 100644 --- a/repair/apps/statusquo/models/indicators.py +++ b/repair/apps/statusquo/models/indicators.py @@ -56,9 +56,9 @@ class IndicatorFlow(GDSEModel): validators=[validate_comma_separated_integer_list], blank=True, null=True) materials = models.ManyToManyField(Material, blank=True) - process_ids = models.CharField( + process_ids = models.TextField( validators=[validate_comma_separated_integer_list], - blank=True, null=True, max_length=100) + blank=True, null=True) hazardous = EnumIntegerField( enum=TriState, default=TriState.BOTH) avoidable = EnumIntegerField( diff --git a/repair/apps/statusquo/tests/test_indicators.py b/repair/apps/statusquo/tests/test_indicators.py index 42892ee18..c684fe53d 100644 --- a/repair/apps/statusquo/tests/test_indicators.py +++ b/repair/apps/statusquo/tests/test_indicators.py @@ -17,6 +17,7 @@ ) from repair.apps.statusquo.views.computation import ComputeIndicator + class FlowIndicatorTest(BasicModelPermissionTest, APITestCase): casestudy = 17 @@ -123,5 +124,5 @@ def test_put_permission(self): pass def test_ComputeIndicator(self): - ci = ComputeIndicator() + ci = ComputeIndicator(self.keyflow_id1) ci.calculate_indicator_flow(self.flow_a) diff --git a/repair/apps/statusquo/views/computation.py b/repair/apps/statusquo/views/computation.py index 20f7c58ea..9a2d7b873 100644 --- a/repair/apps/statusquo/views/computation.py +++ b/repair/apps/statusquo/views/computation.py @@ -1,5 +1,4 @@ from abc import ABCMeta -from enum import Enum import numpy as np from django.db.models import Q, Sum, Case, When, F, Value from collections import OrderedDict @@ -8,9 +7,10 @@ from django.db.models.functions import Coalesce from repair.apps.utils.utils import descend_materials -from repair.apps.asmfa.models import Actor, FractionFlow, AdministrativeLocation +from repair.apps.asmfa.models import (Actor, FractionFlow, Process, + AdministrativeLocation, Material) from repair.apps.asmfa.serializers import Actor2ActorSerializer - +from repair.apps.utils.utils import get_annotated_fractionflows def filter_actors_by_area(actors, geom): ''' @@ -31,53 +31,36 @@ class ComputeIndicator(metaclass=ABCMeta): default_unit = '' is_absolute = True - def __init__(self, strategy=None): + def __init__(self, keyflow_pk, strategy=None): + self.keyflow_pk = keyflow_pk self.strategy = strategy def get_queryset(self, indicator_flow, geom=None): '''filter all flows by IndicatorFlow attributes, optionally filter for geometry''' + # there might be unset indicators -> return empty queryset # (calculation will return zero) if not indicator_flow: return FractionFlow.objects.none() - materials = indicator_flow.materials.all() + materials = indicator_flow.materials.all() flow_type = indicator_flow.flow_type.name hazardous = indicator_flow.hazardous.name avoidable = indicator_flow.avoidable.name + strategy_id = getattr(self.strategy, 'id', None) + + flows = get_annotated_fractionflows(self.keyflow_pk, + strategy_id=strategy_id) + # filter flows by type (waste/product/both) - flows = FractionFlow.objects.all() - - if self.strategy: - # ToDo: material - flows = flows.filter( - ( - Q(f_strategyfractionflow__isnull = True) | - Q(f_strategyfractionflow__strategy = self.strategy) - ) - ).annotate( - # strategy fraction flow overrides amounts - strategy_amount=Coalesce( - 'f_strategyfractionflow__amount', 'amount'), - # set new flow amounts to zero for status quo - statusquo_amount=Case( - When(strategy__isnull=True, then=F('amount')), - default=Value(0), - ) - ) - else: - # flows without filters for status quo - flows = flows.filter(strategy__isnull=True) - # just for convenience, use field statusquo_amount - flows = flows.annotate(statusquo_amount=F('amount')) if flow_type != 'BOTH': is_waste = True if flow_type == 'WASTE' else False - flows = flows.filter(waste=is_waste) + flows = flows.filter(strategy_waste=is_waste) if hazardous != 'BOTH': is_hazardous = True if hazardous == 'YES' else False - flows = flows.filter(hazardous=is_hazardous) + flows = flows.filter(strategy_hazardous=is_hazardous) if avoidable != 'BOTH': is_avoidable = True if avoidable == 'YES' else False flows = flows.filter(avoidable=is_avoidable) @@ -86,11 +69,13 @@ def get_queryset(self, indicator_flow, geom=None): process_ids = indicator_flow.process_ids if (process_ids): process_ids = process_ids.split(',') - flows = flows.filter(process__id__in=process_ids) + processes = Process.objects.filter(id__in=process_ids) + flows = flows.filter(strategy_process__in=processes) if materials: mats = descend_materials(list(materials)) - flows = flows.filter(material__id__in=mats) + mats = Material.objects.filter(id__in=mats) + flows = flows.filter(strategy_material__in=mats) # ToDo: implement new filter attribute sinks and sources only #destinations_to_exclude = flows.exclude(destination__isnull=True).values('destination__id').distinct() @@ -120,7 +105,7 @@ def get_queryset(self, indicator_flow, geom=None): Q(origin__in=origins) & Q(destination__in=destinations)) return flows - def sum(self, flows, field='statusquo_amount'): + def sum(self, flows, field='amount'): '''sum up flow amounts''' # sum up amounts to single value if len(flows) == 0: @@ -201,9 +186,8 @@ def calculate_indicator_flow(self, indicator_flow, areas=[], # single value (nothing to iterate) if (not areas or len(areas)) == 0 and not geom: flows = self.get_queryset(indicator_flow, geom=geom) - amount = agg_func(flows) - strategy_amount = agg_func(flows, field='strategy_amount') \ - if self.strategy else 0 + amount = agg_func(flows, field='amount') + strategy_amount = agg_func(flows, field='strategy_amount') return {-1: (amount, strategy_amount)} amounts = {} geometries = [] @@ -214,9 +198,8 @@ def calculate_indicator_flow(self, indicator_flow, areas=[], geometries.append((area.id, geom)) for g_id, geometry in geometries: flows = self.get_queryset(indicator_flow, geom=geometry) - amount = agg_func(flows) - strategy_amount = agg_func(flows, field='strategy_amount') \ - if self.strategy else 0 + amount = agg_func(flows, field='amount') + strategy_amount = agg_func(flows, field='strategy_amount') amounts[g_id] = (amount, strategy_amount) if aggregate: total_sum = 0 @@ -361,7 +344,7 @@ def calculate(self, indicator, areas=[], geom=None, aggregate=False): res = amount[0] / ha if ha > 0 else None if self.strategy: strategy_res = amount[1] / ha if ha > 0 else None - if (strategy_amount is None or amount is None): + if (strategy_res is None or amount is None): delta = None else: delta = strategy_res - res @@ -374,4 +357,4 @@ def calculate(self, indicator, areas=[], geom=None, aggregate=False): 'delta': delta })) - return results \ No newline at end of file + return results diff --git a/repair/apps/statusquo/views/indicators.py b/repair/apps/statusquo/views/indicators.py index aeda1ca2d..3a00f86d1 100644 --- a/repair/apps/statusquo/views/indicators.py +++ b/repair/apps/statusquo/views/indicators.py @@ -55,7 +55,8 @@ def compute(self, request, **kwargs): if strategy.status == 1: return HttpResponseBadRequest( _('calculation is still in process')) - compute = compute_class(strategy=strategy) + keyflow_pk = self.kwargs.get('keyflow_pk') + compute = compute_class(keyflow_pk=keyflow_pk, strategy=strategy) if aggregate is not None: aggregate = aggregate.lower() == 'true' if areas: diff --git a/repair/apps/studyarea/migrations/0031_auto_20190923_1237.py b/repair/apps/studyarea/migrations/0031_auto_20190923_1237.py new file mode 100644 index 000000000..38f5ae13b --- /dev/null +++ b/repair/apps/studyarea/migrations/0031_auto_20190923_1237.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-09-23 12:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('studyarea', '0030_auto_20190513_1638'), + ] + + operations = [ + migrations.AlterModelOptions( + name='adminlevels', + options={'default_permissions': ('add', 'change', 'delete', 'view'), 'ordering': ['id']}, + ), + ] diff --git a/repair/apps/studyarea/models/areas.py b/repair/apps/studyarea/models/areas.py index 380b36b6d..0d2732667 100644 --- a/repair/apps/studyarea/models/areas.py +++ b/repair/apps/studyarea/models/areas.py @@ -17,6 +17,7 @@ class Meta(GDSEUniqueNameModel.Meta): ('casestudy', 'name',), ) abstract = False + ordering = ['id'] def create_area(self, **kwargs): """Create an area of the according level""" diff --git a/repair/apps/studyarea/tests/test_bulk.py b/repair/apps/studyarea/tests/test_bulk.py index 0b2a5e453..40805077d 100644 --- a/repair/apps/studyarea/tests/test_bulk.py +++ b/repair/apps/studyarea/tests/test_bulk.py @@ -12,6 +12,8 @@ from repair.apps.studyarea.factories import AdminLevelsFactory from repair.apps.studyarea.models import Area, AdminLevels +pd.set_option('mode.chained_assignment', 'raise') + class BulkImportAreaTest(LoginTestCase, APITestCase): testdata_folder = 'data' diff --git a/repair/apps/utils/serializers.py b/repair/apps/utils/serializers.py index 12ef256c9..3d229d00a 100644 --- a/repair/apps/utils/serializers.py +++ b/repair/apps/utils/serializers.py @@ -33,7 +33,7 @@ class MakeValid(GeoFunc): class BulkValidationError(Exception): - def __init__(self, message, path=''): + def __init__(self, message='error', path=''): super().__init__(message) self.message = message self.path = path @@ -210,10 +210,15 @@ def match(x): indicator=True, suffixes=['', '_old']) - missing_rows = df_merged.loc[df_merged._merge=='left_only'] - existing_rows = df_merged.loc[df_merged._merge=='both'] + idx_missing = df_merged._merge=='left_only' + idx_existing = df_merged._merge=='both' - existing_rows[referencing_column] = existing_rows['_models'] + df_merged.loc[idx_existing, + referencing_column] = df_merged.loc[idx_existing, + '_models'] + + existing_rows = df_merged.loc[idx_existing] + missing_rows = df_merged.loc[idx_missing] tmp_columns = ['_merge', 'id', '_models'] if keyflow_added: @@ -221,8 +226,8 @@ def match(x): tmp_columns.append('keyflow_old') if 'keyflow' not in dataframe.columns: tmp_columns.append('keyflow') - existing_rows.drop(columns=tmp_columns, inplace=True) - missing_rows.drop(columns=tmp_columns, inplace=True) + existing_rows = existing_rows.drop(columns=tmp_columns) + missing_rows = missing_rows.drop(columns=tmp_columns) # append the null rows again if self.allow_null: @@ -459,7 +464,7 @@ def to_internal_value(self, data): if self.check_index: df_t = dataframe.set_index(self.index_columns) - duplicates = df_t.index.get_duplicates() + duplicates = df_t.index[df_t.index.duplicated()].unique() if len(duplicates) > 0: if len(self.index_columns) == 1: message = _('Index "{}" has to be unique!')\ @@ -602,7 +607,7 @@ def _parse_columns(self, dataframe): # set the error message in the error matrix at these positions self.error_mask.set_error(error_idx, column, error_msg) # overwrite values in dataframe with parsed ones - dataframe[column].loc[not_na] = entries + dataframe.loc[not_na, column] = entries if error_occured: self.error_mask.add_message(error_occured) return dataframe diff --git a/repair/apps/utils/utils.py b/repair/apps/utils/utils.py index ebc6ab083..884774b43 100644 --- a/repair/apps/utils/utils.py +++ b/repair/apps/utils/utils.py @@ -1,4 +1,14 @@ from repair.apps.asmfa.models import Material +from django.db.models.functions import Coalesce +from django.db.models import (AutoField, Q, F, Case, When, FilteredRelation) +from repair.apps.asmfa.models import FractionFlow + +def copy_django_model(obj): + initial = dict([(f.name, getattr(obj, f.name)) + for f in obj._meta.fields + if not isinstance(f, AutoField) and\ + not f in obj._meta.parents.values()]) + return obj.__class__(**initial) def descend_materials(materials): """return list of material ids of given materials and all of their @@ -34,4 +44,59 @@ def get_descendants(mat_id): mats.extend(get_descendants(material.id)) # fractions have to contain children and the material itself mats.append(material.id) - return mats \ No newline at end of file + return mats + + +def get_annotated_fractionflows(keyflow_id, strategy_id=None): + ''' + returns fraction flows in given keyflow + + annotates fraction flow queryset flows with values of fields + of strategy fraction flows ('strategy_' as prefix to original field) + + strategy fraction flows override fields of fraction flow (prefix 'strategy_') if + changed in strategy + ''' + + queryset = FractionFlow.objects + if not strategy_id: + queryset = queryset.filter( + keyflow__id=keyflow_id, + strategy__isnull=True).\ + annotate( + strategy_amount=F('amount'), + strategy_material=F('material'), + strategy_material_name=F('material__name'), + strategy_material_level=F('material__level'), + strategy_waste=F('waste'), + strategy_hazardous=F('hazardous'), + strategy_process=F('process'), + # just setting Value(0) doesn't seem to work + strategy_delta=F('strategy_amount') - F('amount') + ) + else: + qs1 = queryset.filter( + Q(keyflow__id=keyflow_id) & + (Q(strategy__isnull=True) | + Q(strategy_id=strategy_id)) + ) + qsfiltered = qs1.annotate(sf=FilteredRelation( + 'f_strategyfractionflow', + condition=Q(f_strategyfractionflow__strategy=strategy_id))) + queryset = qsfiltered.annotate( + # strategy fraction flow overrides amounts + strategy_amount=Coalesce('sf__amount', 'amount'), + strategy_material=Coalesce('sf__material', 'material'), + strategy_material_name=Coalesce( + 'sf__material__name', 'material__name'), + strategy_material_level=Coalesce( + 'sf__material__level', 'material__level'), + strategy_waste=Coalesce('sf__waste', 'waste'), + strategy_hazardous=Coalesce('sf__hazardous', 'hazardous'), + strategy_process=Coalesce('sf__process', 'process'), + #strategy_delta=Case(When(strategy=strategy, + #then=F('strategy_amount')), + #default=F('strategy_amount') - F('amount')) + ) + + return queryset.order_by('origin', 'destination') diff --git a/repair/apps/utils/views.py b/repair/apps/utils/views.py index 1d76f0537..554124934 100644 --- a/repair/apps/utils/views.py +++ b/repair/apps/utils/views.py @@ -143,7 +143,8 @@ def filter_fields(serializer, request): for row in data] return data - def _filter(self, lookup_args, query_params=None, SerializerClass=None): + def _filter(self, lookup_args, query_params=None, SerializerClass=None, + annotations=None): """ return a queryset filtered by lookup arguments and query parameters return None if query parameters are malformed @@ -174,7 +175,9 @@ def get_filter_args(self, queryset, query_params=None): for k, v in query_params.items(): key_cmp = k.split('__') key = key_cmp[0] - if hasattr(queryset.model, key): + is_attr = (hasattr(queryset.model, key) or + key in queryset.query.annotations) + if is_attr: if len(key_cmp) > 1: cmp = key_cmp[-1] if cmp == 'in': diff --git a/repair/dump_test_fixture.bat b/repair/dump_test_fixture.bat new file mode 100644 index 000000000..561070a34 --- /dev/null +++ b/repair/dump_test_fixture.bat @@ -0,0 +1,56 @@ +SET DJANGO_SETTINGS_MODULE=%DJANGO_SITENAME%.settings_prod + +REM GOTO REORDER +REM GOTO MERGE +REM GOTO MERGE +REM GOTO END +python manage.py dump_object -k studyarea.stakeholder --query "{\"stakeholder_category__casestudy_id\": 7}" > repair\graph_fixtures\graph_stakeholders.json + +python manage.py dump_object -k changes.solutioninstrategy --query "{\"solution__id\": 89}" > repair\graph_fixtures\graph_solutioninstrategy.json +python manage.py dump_object -k asmfa.fractionflow --query "{\"keyflow__id\": 32}" > repair\graph_fixtures\graph_fractionflow.json +python manage.py dump_object -k asmfa.actor --query "{\"activity__activitygroup__keyflow__id\": 32}" > repair\graph_fixtures\graph_actors.json +python manage.py dump_object -k changes.solution --query "{\"id\": 89}" > repair\graph_fixtures\graph_solutions.json +python manage.py dump_object -k changes.solutioninstrategy --query "{\"solution__id\": 89}" > repair\graph_fixtures\graph_solutioninstrategy.json +python manage.py dump_object -k changes.affectedflow --query "{\"solution_part__solution__id\": 89}" > repair\graph_fixtures\graph_affectedflow.json + +:MERGE +python manage.py merge_fixtures^ + repair\graph_fixtures\graph_stakeholders.json^ + repair\graph_fixtures\graph_solutions.json^ + repair\graph_fixtures\graph_solutioninstrategy.json^ + repair\graph_fixtures\graph_actors.json^ + repair\graph_fixtures\graph_affectedflow.json^ + repair\graph_fixtures\graph_fractionflow.json^ + repair\graph_fixtures\graph_solutioninstrategy.json^ + > repair\graph_fixtures\graph_data_unordered.json + + +:REORDER + +python manage.py reorder_fixtures repair\graph_fixtures\graph_data_unordered.json ^ + auth.group auth.user login.profile login.casestudy login.userincasestudy ^ + publications_bootstrap.type publications_bootstrap.publication ^ + publications.publicationincasestudy ^ + asmfa.keyflow asmfa.keyflowincasestudy asmfa.process ^ + asmfa.composition asmfa.product asmfa.waste asmfa.material asmfa.productfraction^ + studyarea.stakeholdercategory studyarea.stakeholder ^ + asmfa.reason ^ + asmfa.activitygroup asmfa.activity asmfa.actor ^ + asmfa.group2group asmfa.activity2activity asmfa.actor2actor ^ + asmfa.groupstock asmfa.activitystock asmfa.actorstock ^ + asmfa.fractionflow ^ + changes.solutioncategory changes.solution ^ + changes.solutionpart ^ + changes.implementationquestion ^ + changes.strategy changes.solutioninstrategy ^ + changes.implementationquantity ^ + changes.affectedflow ^ + studyarea.adminlevels studyarea.area ^ + > repair\graph_fixtures\peelpioneer_data.json +GOTO END + + +GOTO END + + +:END diff --git a/repair/fixtures/sandbox_data.json b/repair/fixtures/sandbox_data.json index e85492491..b8dac7b5d 100644 --- a/repair/fixtures/sandbox_data.json +++ b/repair/fixtures/sandbox_data.json @@ -753005,7 +753005,7 @@ "fields": { "activitygroup": 12, "done": false, - "nace": "ii.11", + "nace": "ii.911", "name": "Import Inside Country" }, "model": "asmfa.activity", @@ -753105,7 +753105,7 @@ "fields": { "activitygroup": 12, "done": false, - "nace": "ii.22", + "nace": "ii.922", "name": "Import Inside EU" }, "model": "asmfa.activity", @@ -753115,7 +753115,7 @@ "fields": { "activitygroup": 12, "done": false, - "nace": "ii.33", + "nace": "ii.933", "name": "Import Outside EU" }, "model": "asmfa.activity", diff --git a/repair/graph_fixtures/.gitignore b/repair/graph_fixtures/.gitignore new file mode 100644 index 000000000..56d872ccd --- /dev/null +++ b/repair/graph_fixtures/.gitignore @@ -0,0 +1 @@ +!peelpioneer_data.json diff --git a/repair/graph_fixtures/peelpioneer_data.json b/repair/graph_fixtures/peelpioneer_data.json new file mode 100644 index 000000000..514e36b7c --- /dev/null +++ b/repair/graph_fixtures/peelpioneer_data.json @@ -0,0 +1,4235 @@ +[ + { + "fields": { + "name": "Repair" + }, + "model": "auth.group", + "pk": 1 + }, + { + "fields": { + "date_joined": "2017-11-01T18:11:08Z", + "email": "bohnet@ggr-planung.de", + "first_name": "Max", + "groups": [ + 1 + ], + "is_active": true, + "is_staff": true, + "is_superuser": true, + "last_login": "2019-09-25T07:30:45.116Z", + "last_name": "Bohnet", + "password": "pbkdf2_sha256$150000$vGSXfuI2pos9$MHeDpWJG4EzO2QucnHGt9+vEmFLDGw1o7mLZxbUaa/I=", + "user_permissions": [], + "username": "Max" + }, + "model": "auth.user", + "pk": 1 + }, + { + "fields": { + "can_change_password": true, + "organization": "", + "session": "{\"casestudy\": \"2\", \"mode\": 1, \"checkedMapLayers\": [5, 6, 7, 4], \"layerTransparencies\": {\"service-layer-98\": 69, \"service-layer-112\": 55, \"service-layer-127\": 0, \"service-layer-128\": 0, \"service-layer-129\": 25}, \"keyflow\": \"1\", \"wastescapescheckedMapLayers\": [207], \"type\": \"basic\", \"url\": \"https://gdse.h2020repair.bk.tudelft.nl/session\", \"redirected\": false, \"status\": 200, \"ok\": true, \"statusText\": \"OK\", \"headers\": {}, \"body\": {}, \"bodyUsed\": false, \"areaSelects\": [{\"id\": \"1\", \"name\": \"Zaandam Zuid + 15\", \"areas\": [1090, 1089, 1133, 1085, 1166, 1170, 1162, 1073, 1071, 1079, 1083, 1087, 1088, 1082, 1080, 1077], \"level\": \"5\", \"color\": \"#1f77b4\", \"fontSize\": \"30px\"}, {\"id\": \"2\", \"name\": \"Waterland + 4\", \"areas\": [948, 936, 937, 957, 955], \"level\": \"5\", \"color\": \"#aec7e8\", \"fontSize\": \"30px\"}, {\"id\": \"3\", \"name\": \"Vijfhuizen + 2\", \"areas\": [1184, 1200, 1202], \"level\": \"5\", \"color\": \"#ff7f0e\", \"fontSize\": \"30px\"}]}", + "user": 1 + }, + "model": "login.profile", + "pk": 1 + }, + { + "fields": { + "description": "", + "focusarea": "SRID=4326;MULTIPOLYGON (((4.620111000000001 52.33372300000001, 4.629741000000002 52.33731, 4.631058000000001 52.337658, 4.633129000000001 52.33802299999999, 4.646484000000001 52.33986599999999, 4.647629000000001 52.34006900000001, 4.648833 52.340394, 4.672547000000001 52.35061000000002, 4.673481000000001 52.351071, 4.674355000000001 52.35164400000001, 4.675478 52.352692, 4.676254 52.353815, 4.676516000000001 52.35478500000001, 4.676505000000001 52.35594699999999, 4.671549000000001 52.369643, 4.67138 52.37035800000001, 4.671332 52.371334, 4.671734000000001 52.37275800000001, 4.672831 52.37435400000002, 4.674424 52.37566899999999, 4.676254 52.37664600000002, 4.678382000000001 52.377479, 4.679137000000001 52.377653, 4.688820000000001 52.37935099999999, 4.688986 52.37943200000001, 4.689196000000001 52.37976900000002, 4.689522 52.37989000000002, 4.690517000000001 52.38003799999999, 4.691138 52.37980799999999, 4.691853 52.379887, 4.728772000000001 52.386302, 4.730810000000001 52.38642800000002, 4.732262000000002 52.38636, 4.735051000000002 52.38595299999999, 4.745451 52.38374200000001, 4.748513000000001 52.38284800000001, 4.750072000000001 52.382217, 4.754439000000001 52.38003399999999, 4.755259000000001 52.379398, 4.756718 52.37783799999999, 4.756434 52.37776400000001, 4.75753 52.376189, 4.758047000000001 52.37506499999999, 4.758380000000001 52.37401500000001, 4.758596 52.37221800000001, 4.758511000000001 52.37124099999998, 4.757984000000001 52.36923900000001, 4.754902000000001 52.35838400000001, 4.754837 52.35790699999998, 4.755104000000001 52.35683200000002, 4.755684 52.35615, 4.756106000000001 52.35582900000001, 4.757330000000001 52.355203, 4.771740000000001 52.34955800000001, 4.790541 52.34184199999999, 4.791782 52.341027, 4.797326 52.33559600000001, 4.797766 52.335283, 4.815436000000001 52.32789300000001, 4.816404000000001 52.32740600000001, 4.817129000000002 52.32691999999999, 4.817711 52.326438, 4.818426 52.32555700000001, 4.818753000000001 52.32556, 4.818990000000001 52.32505399999999, 4.819147000000001 52.32431699999999, 4.819062 52.32331800000001, 4.816124000000001 52.31349100000001, 4.816447000000001 52.31290099999998, 4.817059000000001 52.312343, 4.817160000000001 52.311977, 4.816686000000001 52.310962, 4.814962 52.31036799999999, 4.813188 52.308259, 4.810131000000001 52.30591900000002, 4.809450000000002 52.30557999999999, 4.80375 52.303604, 4.798981000000001 52.30148800000001, 4.795762000000001 52.299428, 4.794619000000001 52.296596, 4.792687 52.29306500000001, 4.789562000000001 52.28953300000001, 4.788341000000001 52.28853100000001, 4.779910000000001 52.28313000000001, 4.774746 52.28103300000001, 4.766186000000001 52.27722200000002, 4.764013 52.27641800000001, 4.755732 52.27409600000001, 4.754698000000001 52.27392800000001, 4.751903000000001 52.27364699999999, 4.750161000000001 52.273343, 4.746699 52.272253, 4.745557000000001 52.271596, 4.743882 52.269903, 4.739579000000002 52.267748, 4.738663 52.26744800000001, 4.735872000000001 52.26688399999998, 4.730541000000001 52.26542000000001, 4.729428 52.26500500000002, 4.727382000000001 52.26390800000001, 4.725941000000001 52.26337099999999, 4.725164000000001 52.262883, 4.723539000000001 52.260994, 4.720786 52.25878099999999, 4.715840000000001 52.25652800000001, 4.714061000000001 52.25584800000001, 4.71322 52.25540100000001, 4.706951000000001 52.249021, 4.706388 52.24839100000001, 4.705697000000001 52.247234, 4.705267 52.24675500000001, 4.701757000000002 52.24440199999999, 4.701276 52.24417200000001, 4.700372000000001 52.24396699999999, 4.695386000000001 52.243204, 4.692714 52.24272000000002, 4.690603 52.24205799999999, 4.686543000000001 52.24054300000001, 4.682484000000001 52.239549, 4.681249000000001 52.23914299999999, 4.680823 52.23889100000002, 4.680541000000001 52.23860000000002, 4.678488 52.235689, 4.677327 52.234562, 4.676389000000001 52.23395499999999, 4.670293000000001 52.23079500000001, 4.670348000000001 52.23075600000001, 4.669663000000001 52.23036, 4.669618000000001 52.230398, 4.665916 52.228037, 4.665045000000002 52.227574, 4.664156 52.227303, 4.659011 52.22624699999999, 4.657669000000001 52.22582999999999, 4.654098000000001 52.224459, 4.651248000000001 52.22295399999999, 4.646610000000001 52.22097000000002, 4.645568 52.22075899999999, 4.64423 52.22060500000001, 4.643254000000001 52.220309, 4.632635000000001 52.21618400000001, 4.631012000000001 52.21591200000002, 4.618666000000001 52.21448900000001, 4.602762000000001 52.214558, 4.601708000000001 52.21465099999999, 4.597463000000001 52.215727, 4.584354000000001 52.21876500000001, 4.583553000000001 52.218874, 4.581732000000001 52.21879400000001, 4.573409000000002 52.21716700000001, 4.571396000000001 52.21695900000001, 4.570222 52.217028, 4.558433 52.21879900000001, 4.557307 52.21901999999999, 4.556857000000001 52.21914, 4.556532000000001 52.21934100000001, 4.550817000000001 52.225562, 4.550522 52.226102, 4.550470000000001 52.22674500000001, 4.550767000000001 52.22742800000001, 4.552156 52.229181, 4.553625000000001 52.23093299999999, 4.554208000000001 52.231397, 4.555583 52.23201099999999, 4.560452000000001 52.23297199999998, 4.561119000000001 52.23317699999998, 4.562273000000001 52.233843, 4.562661 52.23424599999999, 4.562937000000001 52.23475800000001, 4.567551000000001 52.249216, 4.569521000000001 52.259691, 4.569492000000001 52.260315, 4.567869000000001 52.26729400000001, 4.567972000000001 52.26807200000001, 4.568336 52.268553, 4.568979000000001 52.26901900000001, 4.587943000000001 52.280099, 4.588599 52.28068100000002, 4.591173 52.28572799999999, 4.596048 52.291369, 4.596246 52.29199000000001, 4.596375 52.29417499999999, 4.597087000000001 52.296084, 4.59797 52.297376, 4.604446 52.305987, 4.610550000000001 52.31110300000001, 4.611108000000001 52.31160700000002, 4.611533000000001 52.31224, 4.611701 52.31309700000001, 4.611367000000001 52.323677, 4.611453000000002 52.32468300000001, 4.611662000000001 52.32541999999999, 4.612144000000001 52.32641799999999, 4.612661000000001 52.327201, 4.616592 52.33150400000001, 4.618211000000001 52.33278400000001, 4.620111000000001 52.33372300000001)))", + "geom": "SRID=4326;MULTIPOLYGON (((5.35077200094509 52.40020899824248, 5.350015000031366 52.39977399941099, 5.349528000708233 52.39930099916766, 5.348167999829974 52.39702799980329, 5.347717001096582 52.3967229999876, 5.346371001077102 52.39611399879074, 5.345913000498089 52.39645399904965, 5.344371999644567 52.39588399911543, 5.342293000992831 52.39597599955204, 5.34010800028992 52.39594099910884, 5.339352999094242 52.39581299979445, 5.33861099889832 52.39558099900128, 5.33237000067456 52.3932460003881, 5.332934000472287 52.39277299872806, 5.313516998838177 52.38574099982988, 5.313040000937733 52.38625299992071, 5.303826999661734 52.38302699827455, 5.301245001110042 52.38198599921233, 5.31668499975102 52.36843199894263, 5.376197999520154 52.3167419988816, 5.378549999339549 52.31470200013476, 5.37899299920076 52.31444599867027, 5.379210999624785 52.31399499797112, 5.354006001050815 52.30940699978137, 5.351501000141255 52.30923499994795, 5.348963001051484 52.30930199845037, 5.348616998845321 52.30916499906061, 5.348147999817058 52.30938199926215, 5.346955000887744 52.30952299947141, 5.344924001134022 52.3099389997272, 5.344176998810525 52.3101719989581, 5.333504999283173 52.31427399883698, 5.320221999627547 52.30535899892489, 5.317171999074152 52.30356499888246, 5.309097998675319 52.30742999871511, 5.304470999511123 52.30950899872745, 5.297303998706649 52.31250699901791, 5.291593999267945 52.31476099983055, 5.286801000846289 52.31652099927703, 5.279090000257003 52.31914999937869, 5.273371999113656 52.32096999978674, 5.262778999778944 52.32402099783015, 5.252888000614323 52.32649399995754, 5.245132000704093 52.32821100015067, 5.235098998898556 52.33015099964906, 5.223656000431163 52.33217099995838, 5.223445998879319 52.33192199886653, 5.223030000876644 52.33199000005197, 5.221265000444959 52.33135899929848, 5.22089800119932 52.33114099924687, 5.220697001211142 52.33087399942403, 5.220498000941008 52.33080200025151, 5.220438000902262 52.33087499927484, 5.220663000339447 52.33097999918927, 5.220915998661729 52.33167999956284, 5.220170998888738 52.33252699925002, 5.220428999338596 52.33289599943473, 5.221930000166287 52.33345399833234, 5.221890999999479 52.33349899870027, 5.221345000496622 52.33335999960941, 5.221227999996197 52.33347999870229, 5.220607999595813 52.33325799924747, 5.221195998842546 52.33351399787878, 5.220919001071259 52.33381699941209, 5.220141000285596 52.33396999924685, 5.219348998641155 52.33370199957331, 5.21903000112599 52.3330719986708, 5.220143999862664 52.33188899954084, 5.220040000361996 52.33172799806704, 5.219290001293892 52.33144099839539, 5.219062999306201 52.33105699903209, 5.21973200129608 52.33049899871806, 5.216669999601952 52.32942500032157, 5.216184999996864 52.32939899995161, 5.213633000048314 52.32984199901068, 5.213398999047463 52.3293619983902, 5.213297999123863 52.32937800025197, 5.213512000111798 52.32986299871031, 5.212662000979117 52.33005099898873, 5.21271799874931 52.33060400004871, 5.212395998824617 52.33062499974834, 5.211850999180783 52.33080999905806, 5.211685999782344 52.33097300023353, 5.211304999677925 52.33086899875371, 5.211052001355643 52.3309159988233, 5.209646001297417 52.3315309991313, 5.209543998682332 52.33185999961844, 5.209160998859868 52.33199700042393, 5.208567000458999 52.33255699902325, 5.20828700027818 52.33256099984274, 5.206245999101769 52.33332399931506, 5.206101999575269 52.33349499929701, 5.205976000343639 52.33459299977733, 5.204770000414569 52.33550999893493, 5.2008510005746 52.33611900013759, 5.197735000828322 52.33642099898751, 5.194601000787175 52.33650099979894, 5.191374999553631 52.33638199914072, 5.1889160006565 52.33613899998638, 5.186427000323763 52.3357579987596, 5.179094000261829 52.33431899816645, 5.178954000171419 52.33412999945356, 5.178420998835858 52.33402499812298, 5.178250000283284 52.33414799960074, 5.166858000290972 52.33186299917089, 5.15748199933458 52.32923700004314, 5.156698999253805 52.32908199909041, 5.155771999646529 52.32901299805411, 5.152956999953008 52.3291239984897, 5.15214400126909 52.32891799948029, 5.151157998649628 52.32795899950621, 5.151619998664732 52.32851300041719, 5.151485000701898 52.32877199860113, 5.151268999995918 52.32890300030178, 5.151006000250947 52.32902899991588, 5.15016100041338 52.32917699908044, 5.149130001305435 52.32911199886365, 5.147382001309599 52.32851799967128, 5.147091999706092 52.32837899916408, 5.146467000010595 52.32775199922973, 5.146038001008165 52.32777699974888, 5.144803999361535 52.32707299997158, 5.144553000757298 52.32672799903865, 5.144396000231042 52.32624499886548, 5.144410001089821 52.32562000004877, 5.145000999913623 52.3255309991636, 5.145020000067515 52.32537199880749, 5.145116000696003 52.32549599872003, 5.145209998773982 52.32545800014021, 5.144299999602555 52.32421399877906, 5.144706998874025 52.32395199821002, 5.144664999130148 52.32386599970972, 5.144434000538828 52.32389899903546, 5.143792000407483 52.32421299892824, 5.142382000913166 52.3250189990679, 5.140150001171265 52.32659299935091, 5.137334998645282 52.32516699823257, 5.138429000201503 52.32443699950122, 5.139748001194908 52.32338299953635, 5.140776000725785 52.32221599943394, 5.140793001161631 52.32204999870579, 5.140637000494397 52.32202999885694, 5.140444999237424 52.32212500026331, 5.140062999273982 52.32202099878334, 5.139896000157499 52.3217579983634, 5.139673000438359 52.32170999844291, 5.139815000246814 52.32148100003194, 5.140071000978626 52.32140299892197, 5.140185998928544 52.32122199901526, 5.1409400002652 52.32119199924198, 5.141446999601248 52.32097700015871, 5.142187000079124 52.320121998831, 5.142571999619634 52.31986899833539, 5.143179998879282 52.31918799937478, 5.143512000226666 52.31860099913723, 5.144368001345945 52.31773999870428, 5.144577000206304 52.31739399933623, 5.146144000226879 52.31609800006591, 5.146529999626411 52.31587199837478, 5.146722000883384 52.31561400004112, 5.147351000014972 52.31529399962677, 5.147609000464829 52.31493799891793, 5.151243000828867 52.31252799871761, 5.151314999175885 52.31234299940736, 5.150966000225116 52.3121539992776, 5.151300001290545 52.31167899932613, 5.152230000474889 52.31189599952759, 5.152778999554814 52.31176900006232, 5.154459000639722 52.31068799845827, 5.155763000915325 52.30998399867909, 5.157778000092224 52.30902899952167, 5.159603000562655 52.30828299892666, 5.159888000038586 52.30804899842867, 5.160149999924533 52.30759299847497, 5.160170999796472 52.3072279991083, 5.160057998732137 52.30705999867811, 5.160514999452127 52.30691299794763, 5.160982998621368 52.30664499968916, 5.161431000610153 52.30620499876475, 5.162854001104227 52.30579599888068, 5.162994001194637 52.30568699956256, 5.162967999195123 52.3055490003219, 5.163220000490844 52.30562799986668, 5.163438000914869 52.30559600039168, 5.163623000326224 52.30548799950816, 5.163669999365213 52.30530099907984, 5.164091999354486 52.30520700035649, 5.164994999512755 52.30519799886664, 5.165655999797991 52.30493499986222, 5.166560999674306 52.30476599958114, 5.166801999688316 52.30457300004787, 5.167526999448389 52.30449199938521, 5.167630998949059 52.30432499880577, 5.16819299902874 52.30415399882303, 5.168514998953435 52.30414599860023, 5.169032999571194 52.30449499893768, 5.169771000331025 52.30476499973032, 5.17114799881267 52.30450699856378, 5.171326999069888 52.3043689993231, 5.171327998928911 52.30420499971234, 5.171520000185884 52.30396699839469, 5.171985999637079 52.3038999998922, 5.172873999077546 52.30407499927824, 5.173771000081679 52.30393799988837, 5.17417799935315 52.30411499897605, 5.174951000843699 52.30420399986151, 5.175667998899129 52.30444099849591, 5.1764420002487 52.30437899924755, 5.176913998854031 52.3044759989396, 5.177555998985374 52.3043769995459, 5.177801001267937 52.30459599944924, 5.178252000001329 52.3046969999608, 5.178410000386608 52.30471400025723, 5.178779999209316 52.30458499967397, 5.179277999814159 52.30489200061194, 5.180052001163731 52.30492400008694, 5.18046200001227 52.3050889995485, 5.180897001001298 52.30501199828914, 5.181474998825344 52.30511599976938, 5.181942000968022 52.30535799907407, 5.182444001008957 52.30544599869248, 5.182777999241925 52.30575499933206, 5.183631000784136 52.30587399857464, 5.183941999427118 52.30638199926859, 5.18374800128456 52.30660799954384, 5.183821999349625 52.30691099824598, 5.183989001298571 52.30710600031327, 5.184390001415904 52.30727099835855, 5.184983999816774 52.30724099858521, 5.185521000588427 52.3069750000284, 5.186401001156712 52.30686399817621, 5.186758998838685 52.30728499910251, 5.18692600078763 52.30735199902118, 5.187402998688074 52.30745099983105, 5.18785500011295 52.30744099849038, 5.188674000783466 52.30767399913758, 5.189093001195671 52.3075819986997, 5.189525999634194 52.30761299832386, 5.189625999698771 52.30772699831228, 5.190362000740556 52.30780000016832, 5.190512999280216 52.30773799950377, 5.19077399930714 52.30738499834699, 5.191178998860566 52.30742599931182, 5.191818999273864 52.30761199847303, 5.192343998904782 52.30829099914946, 5.193052001061471 52.30857399941885, 5.193441000038071 52.30858299949248, 5.19385400129614 52.30888099894042, 5.194184000093015 52.30893799893462, 5.19482600022436 52.30887100043219, 5.195222001046581 52.30901699989558, 5.195605000869044 52.30891499953327, 5.196752000618391 52.30898899840767, 5.197561999725242 52.30876699895201, 5.197864999496042 52.30885499998659, 5.198503000191296 52.30879899842699, 5.199153999053844 52.30893999863626, 5.200799999267057 52.30894299960496, 5.201257999846071 52.30909599944032, 5.201722999438243 52.30907399988978, 5.202111001388282 52.3089449993066, 5.202519000518775 52.30899899833212, 5.202681000340144 52.30895399938022, 5.203131999073537 52.30921300039741, 5.20416200115492 52.30921599994988, 5.204386000733082 52.30938600008166, 5.204793000004552 52.30945599813655, 5.205797999945444 52.30938300052919, 5.206124999165253 52.30965899901034, 5.207199000708558 52.30967199848725, 5.207598001107847 52.30953399924667, 5.208029999687346 52.30960899938812, 5.208409999932742 52.30985899891543, 5.209058999077245 52.30996099927773, 5.209856999875822 52.30971299945207, 5.210676000546338 52.30932099844844, 5.211210998767484 52.30954799999063, 5.211566999563873 52.30992599883407, 5.211966999822184 52.30991899987832, 5.212401000952191 52.30952999984339, 5.212494999030172 52.30954799999063, 5.212346000208559 52.30975399900067, 5.212376998670724 52.30987799891349, 5.212812999518775 52.31010199948699, 5.213915999806201 52.31014599858805, 5.2144700010137 52.31031199931651, 5.214824999118605 52.3102699999171, 5.215204999364001 52.31046199959938, 5.215777000866371 52.31036699819283, 5.216252998907793 52.31042100005078, 5.217213999386763 52.31032099797392, 5.217923001402474 52.31055599973887, 5.219056000293042 52.31066199808822, 5.219788998925297 52.31085199948507, 5.220257000926999 52.31083299807079, 5.220324999837928 52.31074699957031, 5.220774998712297 52.31071599852995, 5.220947999815378 52.31058099884186, 5.221095998777969 52.31075900061263, 5.221649999985469 52.31094100037044, 5.222167000744205 52.31098699917315, 5.222598999323705 52.31090099925647, 5.223526998790003 52.31114699938043, 5.224181000062082 52.31103199954124, 5.225009999322824 52.31110200042855, 5.225350999401412 52.31122199810564, 5.225731999505831 52.31116199855899, 5.226336999188411 52.31121699885153, 5.226893000113956 52.31105599879343, 5.22761400043794 52.31131999906463, 5.228615000942741 52.31119699900264, 5.228987999342516 52.31139999846017, 5.229385999882783 52.31139299808819, 5.230015998873393 52.31155899881661, 5.230406000541477 52.3118639986364, 5.230613999542815 52.31190199863251, 5.230852999838779 52.31185699826442, 5.23103699939111 52.31172599939587, 5.231552000431801 52.3116819988786, 5.232824999412777 52.31187799938034, 5.233690999122284 52.31172099872554, 5.234059001059407 52.31177899998676, 5.234532999382783 52.31168299872942, 5.234935999218163 52.31172299842718, 5.235714999862848 52.31157599911303, 5.236116999839204 52.31175499790215, 5.236733000803497 52.31171599947143, 5.237184999395912 52.31178599894251, 5.239009999866343 52.31159700022896, 5.239256999034488 52.31162400044982, 5.239602001381629 52.31150599964194, 5.23986900056269 52.31166699970004, 5.240183998641764 52.31171199865192, 5.240732000695128 52.3115789986655, 5.241224999172397 52.31160899985504, 5.241588998840967 52.31139199823736, 5.241903999752504 52.31148899934553, 5.242246999549137 52.31141899987444, 5.242342000318601 52.3113599987624, 5.242243999972068 52.31100399946956, 5.242520000716796 52.31098599932232, 5.24249299885826 52.31135199853961, 5.24273399887227 52.31164400029869, 5.244518999316869 52.31255199938599, 5.245646998912322 52.3134699983976, 5.245721999668872 52.31380299828895, 5.246327999210475 52.31408699982514, 5.246546999493524 52.31453399970476, 5.247049999393481 52.3149020000397, 5.247590999601225 52.31560200041504, 5.248481998618759 52.31681700043918, 5.248486000887312 52.31708199914475, 5.248609000541874 52.31715799913692, 5.248812000248098 52.31705899974342, 5.248740998927639 52.31685899842233, 5.248567000797998 52.31677799917605, 5.246985999918644 52.31439199964479, 5.24416600093001 52.31195399937257, 5.244279999020905 52.31189499967676, 5.242817001333462 52.31060499951025, 5.244069000442499 52.31007099986284, 5.244920999293226 52.31081899874306, 5.245161999307236 52.31079499949089, 5.245356000282255 52.30990800010305, 5.245104998845557 52.30957500021149, 5.244707001137753 52.30931699904514, 5.244964998755147 52.30915599898699, 5.245163999025281 52.30930899882235, 5.245537000257518 52.3091279989153, 5.245659000053057 52.30925999905094, 5.245474000641703 52.30933699889404, 5.245406998757336 52.30929399964379, 5.245301999397645 52.30939199918659, 5.24571400079669 52.30995999942691, 5.245471001064635 52.31122099825481, 5.245583999296509 52.31159700022896, 5.246132001349872 52.31242499850452, 5.247061000675193 52.31329799856428, 5.247186000047799 52.31331599871151, 5.247311999279429 52.31305099858957, 5.247715998973831 52.31291599890153, 5.247362000727949 52.31253899849287, 5.247775999012578 52.3123029997096, 5.247584000588066 52.31198499899671, 5.247834999192302 52.31191499810942, 5.247768000140397 52.31180799990926, 5.248603001387737 52.31124199795452, 5.24883799941515 52.31096699932427, 5.249148001031571 52.310955999549, 5.249918999971613 52.31072499860357, 5.250154000831486 52.31084699881474, 5.249804999048256 52.31117599930295, 5.249680999534672 52.31110300027938, 5.249462999110646 52.31120599907627, 5.249555000303042 52.31126600003913, 5.249291000699049 52.31149699956833, 5.24915500004473 52.31139799875852, 5.248804001375916 52.31160099963225, 5.248545001067036 52.3118929985589, 5.248262001309151 52.31231800030437, 5.248062001179994 52.31325799886653, 5.247782000999177 52.31381499933125, 5.249450000943351 52.31640799913958, 5.249623999072993 52.31644900010436, 5.249155999903753 52.31570499921186, 5.24957600017498 52.31560200041504, 5.249805998907279 52.31605399813246, 5.249839999778974 52.31582799927376, 5.25009400079274 52.31569199973496, 5.249943999279642 52.31516899986322, 5.249775000445114 52.31507200017132, 5.249851001060685 52.31492499944103, 5.249775000445114 52.31477099975499, 5.249513000559165 52.31456000007478, 5.24946800123822 52.31402800012934, 5.249637999931773 52.31392399864924, 5.249562999175224 52.31358999890708, 5.249761999445356 52.31350799980992, 5.249873000791646 52.31360899890512, 5.251024999836106 52.31249599924264, 5.251688999698411 52.312344999109, 5.252160001277181 52.31181799983369, 5.252238998637358 52.31156099851827, 5.251901000968301 52.31130499988608, 5.251887999968543 52.31118499796034, 5.253213000116085 52.30931799889596, 5.253173000090254 52.30920699987626, 5.252856999319696 52.30902299900053, 5.252815999434842 52.3087819995468, 5.252913999781375 52.30877599902563, 5.25298099883328 52.30899099810932, 5.253282998745059 52.30918199935704, 5.253441998989361 52.30923299883008, 5.253627001233177 52.30918099950621, 5.253744998760163 52.30900299915163, 5.253732000592867 52.30863199926389, 5.254616000597244 52.30808899954267, 5.255015000996533 52.30795599813996, 5.255184999690084 52.30799900022265, 5.255383999960218 52.30793499844025, 5.255513998627938 52.30776299860678, 5.255349999088523 52.30750699997444, 5.255443999998964 52.30738499834699, 5.256832999621343 52.30660099917186, 5.258281999282469 52.30627100024881, 5.259357000684797 52.30613999996392, 5.260324000317903 52.3061110000414, 5.263053000805626 52.30640199911748, 5.263468998808301 52.30658500014247, 5.263833001309333 52.30658699984411, 5.264233998594205 52.30680400004574, 5.264316001196375 52.30670599908668, 5.26395099883632 52.30614699891969, 5.264050998900897 52.30610399966942, 5.263913001360995 52.30570699799524, 5.264280000606635 52.30571399836722, 5.264629999416426 52.30635999971803, 5.264861000840209 52.30648499948176, 5.265227000226826 52.30610599937107, 5.266177999283108 52.30574000015349, 5.271804999093082 52.30469899966245, 5.274064000693519 52.30414499874941, 5.27533100052036 52.30365799917124, 5.27816000107266 52.30228500005497, 5.278452999420773 52.30223699871812, 5.278728000306478 52.30243599877261, 5.278899998718074 52.30234499960169, 5.278891999845893 52.30219299820077, 5.279372000155867 52.30194699949266, 5.279433999912659 52.30146799872092, 5.279762998850513 52.30131899970488, 5.280208001262231 52.30130499896092, 5.280278999750227 52.30103199861566, 5.280540999636175 52.30089699892737, 5.280676000431471 52.30099599832112, 5.280570001212757 52.3010529983154, 5.281132001292438 52.3013859982074, 5.281323999716951 52.30127199963507, 5.280841999688931 52.30089900004523, 5.280768998650428 52.30093899974307, 5.280623999264906 52.30085299841003, 5.281375001024493 52.30046499822536, 5.28151099884635 52.30056299918452, 5.281378000601562 52.30063999902772, 5.281946999694402 52.30095199921999, 5.282125000092597 52.30083499967897, 5.281657000923357 52.30046899904487, 5.281557000858779 52.30051799881635, 5.281422000063483 52.30044099897315, 5.282165000118427 52.30005200035387, 5.28229199920908 52.30013299818414, 5.282172998990609 52.30020199922075, 5.282734999070291 52.30051899866717, 5.282940001327022 52.30040200054236, 5.282490999479213 52.30003199908873, 5.282385000260499 52.30009299990252, 5.282241000733999 52.30001199782359, 5.282988000225034 52.29961599883229, 5.283107000443505 52.29969599964419, 5.283013999392086 52.29977299948739, 5.283543001291557 52.30008200012724, 5.283760998883121 52.2999809996156, 5.283286000700723 52.29961099816195, 5.283187000495167 52.29966399875293, 5.28307299957181 52.29957099988032, 5.283802998626998 52.29918600066427, 5.28391499983231 52.29925899827175, 5.283809000613596 52.29931999908555, 5.284343998834741 52.29966899800706, 5.284576000117546 52.2995549994347, 5.284094000089527 52.29917399962195, 5.284000999038108 52.29923099961623, 5.283881998819638 52.29914499828318, 5.285081999594572 52.29851500020969, 5.285458000403877 52.29875199884429, 5.285580000199416 52.2986870000434, 5.28543400095487 52.29844099850272, 5.286763000538503 52.29790999840675, 5.291327000086884 52.29657299958379, 5.291997998962348 52.29659700025224, 5.293681999483347 52.29705499849216, 5.294450998705343 52.2971579987055, 5.29463300137209 52.2971539993022, 5.294927999438249 52.29700999954019, 5.295357001273142 52.29724799802567, 5.295985000545706 52.29721899951932, 5.298176000402753 52.29780200035552, 5.29967900094849 52.29863099848371, 5.300763001082021 52.29950399996157, 5.30152900072695 52.30033699890905, 5.301974000306206 52.30098199899336, 5.301998999614235 52.30112799987316, 5.301773000318027 52.30115699837948, 5.301781999049232 52.30127999985788, 5.301967001293047 52.301695998698, 5.302311000948703 52.30210199902984, 5.302770001386738 52.30219699902029, 5.304387999882393 52.3022129994659, 5.304679001344923 52.30201499926221, 5.306001998941957 52.30200599918859, 5.306893000791954 52.30241499907288, 5.307332001217073 52.30272899896672, 5.307984999797667 52.30339799830301, 5.308667999813865 52.30441899894536, 5.308751999301618 52.30478699928087, 5.307914001309671 52.30507400036993, 5.308097001002979 52.30519899871747, 5.309958999089711 52.30456999907918, 5.313898998801618 52.30269799934253, 5.315761999579835 52.30164899862818, 5.317220000804626 52.30053999978313, 5.317113998753451 52.30047299844817, 5.316953998650126 52.30050899874271, 5.316375000967059 52.30087499796058, 5.315788998605909 52.30102599951071, 5.315437999937095 52.30148299931571, 5.314821998972803 52.30160299982541, 5.31432099879089 52.30161199989905, 5.313901001352125 52.30139699798268, 5.313445000491158 52.30094799981671, 5.312827999667843 52.30088800026995, 5.312141000215554 52.30065199865382, 5.311983999689298 52.30052899859163, 5.311961999958337 52.30029999876369, 5.311492001071052 52.30004099916236, 5.311063999095182 52.29991799910015, 5.310252000270286 52.29983699843744, 5.309689000331582 52.29999299924172, 5.309846000857839 52.30017199803115, 5.310225001244212 52.3002459997381, 5.310678999554672 52.30045999897124, 5.311022999210328 52.30049699911661, 5.311065998813227 52.30064399843101, 5.311936000791285 52.30098399869502, 5.312622000384551 52.30139699798268, 5.312751999052272 52.30143000014096, 5.312943000450224 52.30133399888346, 5.313176998618612 52.30167499899826, 5.314056999186898 52.3020649988845, 5.313993999571083 52.30212499984744, 5.313738998698294 52.30212399999662, 5.309298998663497 52.30019099802924, 5.301269000559047 52.29729599936255, 5.300331999529083 52.29685799955535, 5.298044999043549 52.29608600000505, 5.295797998583845 52.29511299928204, 5.295873999199417 52.29487099997696, 5.296687000715797 52.29532799836619, 5.299102000151006 52.29626899961411, 5.300130999540904 52.29656499794476, 5.301396999508722 52.29712599923046, 5.301819999357018 52.29725699951553, 5.302377000141586 52.29731900018017, 5.303302000030816 52.29767100007047, 5.303528999186046 52.29771599902244, 5.303780000622744 52.29763599962674, 5.303886999700481 52.29769599917351, 5.30382899937978 52.2978239999061, 5.304215998638334 52.2979989992923, 5.30434699999754 52.2978699987089, 5.304491999383062 52.29788999997405, 5.304478001356744 52.29811299786469, 5.304607000165442 52.29819599964529, 5.305773999927704 52.29860299841197, 5.306005001351487 52.29857199878776, 5.306261999109859 52.29880099861576, 5.306517999841671 52.29882199973174, 5.306906998818271 52.29897899897064, 5.307022999459674 52.29893399860246, 5.306899999805113 52.29859799915785, 5.306700999534979 52.29843099857826, 5.30581599967158 52.2980899984633, 5.305383001233059 52.29778699976073, 5.304967000397922 52.29776299909228, 5.304171999176413 52.2974079982333, 5.304001000623838 52.29718999818053, 5.303787999494926 52.29578399973699, 5.303538000749712 52.29540899902896, 5.302919000208352 52.2950179992915, 5.302594000706589 52.29490299945201, 5.302251000909957 52.29491599892896, 5.301272000136116 52.2947689981982, 5.300325000515924 52.29477299901772, 5.299247999395551 52.29428099876849, 5.29868599931587 52.29383400030366, 5.297933000670699 52.29360999831339, 5.297387001167841 52.29323899842475, 5.29690099887127 52.29227399934035, 5.297397999617091 52.29179299886624, 5.297356999732237 52.2915899994081, 5.297084001397041 52.29140699979889, 5.296777999216708 52.29138099801255, 5.296505000881512 52.29153799866786, 5.296223000982648 52.29143099905114, 5.296042001007384 52.29109000035201, 5.296117998790494 52.29061199943028, 5.295852999327479 52.29057099988155, 5.295704000505865 52.29043399907518, 5.295654998916368 52.29008899955648, 5.295448999633076 52.28975899921636, 5.295727000095849 52.2892329983738, 5.296338998791589 52.28932799978065, 5.296591999946332 52.28909399928196, 5.296567000638303 52.28891799862847, 5.29625299958579 52.28875999953849, 5.296166000520969 52.28858599858664, 5.29583199945554 52.28858899955534, 5.295995998994956 52.28849399956469, 5.29616999995706 52.28811299975128, 5.296131999649274 52.28729599983164, 5.296363001073057 52.28687499890403, 5.297015999653651 52.28640699932268, 5.298091001055978 52.28530799898323, 5.299634998654106 52.28410199902675, 5.300556998966268 52.28314699986546, 5.301047000698931 52.28279799952684, 5.302618000155595 52.28078299845603, 5.302660999758494 52.28063699899201, 5.302553000821735 52.28053799959793, 5.302779999976965 52.28059299847452, 5.302941999798335 52.2805469996716, 5.303503000018994 52.27964299998306, 5.303884999982436 52.27942899933299, 5.303986999765058 52.27918700002731, 5.304970999834013 52.27830300018757, 5.304952999539143 52.27820199967559, 5.304554998998876 52.27814999893524, 5.305248000437762 52.27818299826122, 5.305535999490763 52.27804099961664, 5.305601998683645 52.27793799940302, 5.30518200124488 52.27763799883575, 5.305235999297029 52.27760000025565, 5.305802998671824 52.27779999874553, 5.30613999931432 52.27776699941957, 5.306425998649274 52.27764699890942, 5.306464998816083 52.27760599936061, 5.305834999825473 52.27732899819481, 5.297468001078527 52.27834599943802, 5.290854998649092 52.27906399996471, 5.287344000772078 52.2795249991746, 5.286998001398377 52.27964000043058, 5.28541200122391 52.27969699900883, 5.284007001024706 52.27990699883935, 5.283667000805141 52.27989699891486, 5.265977999153952 52.28192599931374, 5.243979998827172 52.25453499946469, 5.245093000537285 52.25443099939985, 5.222900999235485 52.22526099854702, 5.224729999142006 52.22519699959621, 5.221336998791978 52.21771699935985, 5.216486999908644 52.20636699996897, 5.217213999386763 52.2062329987133, 5.192585998777815 52.17783299891929, 5.189316000914811 52.17768099893162, 5.143357999277477 52.18026999939192, 5.143208000596841 52.17993599964276, 5.141703000333059 52.18008099925826, 5.135503999020715 52.18034199856614, 5.135337999763254 52.18040899990236, 5.134017998910826 52.18047399870446, 5.132590998980661 52.18047399870446, 5.131043998973003 52.18058499914263, 5.128189999112673 52.18064099928713, 5.121175999398366 52.18094699896401, 5.111209999477195 52.17916999918335, 5.082452000449873 52.17384199939094, 5.070770998713076 52.17122799982449, 5.070204999197305 52.17119499908176, 5.070202999479259 52.17111799923699, 5.069555000193779 52.17097099850339, 5.060796999919726 52.16910099842922, 5.047735000265194 52.16618599985369, 5.046421001399363 52.16592899853239, 5.046223000988252 52.1660140000164, 5.045698001357334 52.16770099906251, 5.045490999382558 52.16898600000369, 5.044395000940753 52.1722149984782, 5.043985999118776 52.17459399910674, 5.043607998591425 52.17568799879482, 5.042049000275495 52.18337000010745, 5.041548999952605 52.18375199836174, 5.036275001361952 52.18389200013915, 5.032503000625551 52.18411299833211, 5.032548999805519 52.18431499935904, 5.032969999935769 52.18447599942035, 5.033198998809045 52.18471699887878, 5.033634999657096 52.18604899847097, 5.033908000824756 52.18625500031743, 5.034619999585074 52.18648999925452, 5.034977000240485 52.18680299930323, 5.035159000074771 52.18837799947117, 5.035707999154695 52.18920099992475, 5.035422999678764 52.18995299963873, 5.035336000613944 52.19069999868221, 5.036221000477342 52.19148899995823, 5.036516001375962 52.19355199956247, 5.03677800126191 52.19386299849293, 5.037791999934006 52.19451599881096, 5.038133999871617 52.19516299860766, 5.038125001140413 52.19567599856462, 5.037765000907932 52.19656499908625, 5.037123000776589 52.19662599848481, 5.037423000970322 52.19741399849304, 5.037201001110205 52.19794899941696, 5.037125000494633 52.19805299948274, 5.034335999968164 52.19810899962709, 5.033612000067112 52.1983239987145, 5.032202000572795 52.19853699810022, 5.03182599976349 52.19843699885405, 5.031694001377724 52.19865699861186, 5.031002999656883 52.19874800061671, 5.030458000013049 52.19923299908498, 5.029849000894378 52.19953399950677, 5.029549000700644 52.19956399928063, 5.028782001196694 52.19986300000072, 5.027942000654239 52.19995799999268, 5.02794999952642 52.20008599789479, 5.027246999497308 52.20040599831493, 5.026060999581152 52.20137399979818, 5.025632000578721 52.20146099815096, 5.023470999324816 52.20152400008361, 5.023172998849128 52.20159599925757, 5.022768999154725 52.20153400000822, 5.021293000467525 52.2018859999038, 5.021432000698912 52.20203600018922, 5.022790998885686 52.20171300021664, 5.02301199888678 52.20174699939384, 5.022458000511742 52.20222299920434, 5.02158699867466 52.20247499843754, 5.021603999110509 52.20254199977352, 5.021919000022044 52.20250099880802, 5.022678000653814 52.20224099935189, 5.02302599974556 52.20203899974173, 5.023109999233313 52.20274299953282, 5.022525999422671 52.20267599819684, 5.022454001075651 52.20281499870678, 5.022621000192135 52.20394099928176, 5.023432999017031 52.20382799914231, 5.023314998657582 52.20305099891032, 5.024689000394621 52.20298899966097, 5.024673999676819 52.20286299862816, 5.024995999601513 52.20284399862977, 5.025036999486368 52.2032710000842, 5.025431000590542 52.20325599948917, 5.025599999425071 52.20398199883103, 5.036649999479773 52.20213799913702, 5.037421001252277 52.20496499949255, 5.035315000742004 52.20523299917151, 5.036445000055505 52.20956799976865, 5.033400998656246 52.20983599944742, 5.033638999093187 52.21032999940483, 5.034092000377086 52.21213499924893, 5.034202998890914 52.21213099842936, 5.034338999545232 52.21289999843732, 5.034354000263034 52.21311700005835, 5.03373000042656 52.21312299916337, 5.033788000747262 52.21371399881397, 5.03669900106927 52.21364800016138, 5.036823000582855 52.21402199960716, 5.036666000056599 52.21419599914483, 5.037289999893072 52.21421499914317, 5.0372569988804 52.21432399987908, 5.036992999276406 52.21440899853002, 5.035459000268505 52.21438399942664, 5.035329998627345 52.21446199912172, 5.035770998770511 52.21455699911346, 5.035682999846667 52.21512499936222, 5.036908999788653 52.21515599898684, 5.036693998941695 52.21569099990927, 5.03702300071201 52.21590799869777, 5.037145000507549 52.21620300001374, 5.036916998660835 52.21624399956294, 5.037129999789747 52.21654999923812, 5.036845000313816 52.21719199836217, 5.036524000248144 52.21738699901599, 5.035848999104127 52.21752899907813, 5.036666000056599 52.22435999981936, 5.039196000274188 52.22402399895461, 5.039341999518733 52.2244109992931, 5.041906000608018 52.22399299933005, 5.041923001043865 52.22406199895126, 5.042633000086138 52.22386599986304, 5.04279599976653 52.22403999940043, 5.041984000941635 52.2243269990769, 5.041929000198001 52.22408899917249, 5.036965000391309 52.22477599866451, 5.03710200090465 52.22534199921068, 5.038731000682017 52.22517899803251, 5.039159999684447 52.22601199840592, 5.040045999406869 52.22703099935898, 5.04048899926808 52.22694199988866, 5.040966000000985 52.22753099842009, 5.040842000487402 52.22769299833114, 5.041353999118563 52.22808699904147, 5.042042001262336 52.22839599826856, 5.042485001123547 52.22877999905426, 5.041102000655304 52.2293219989314, 5.041313999092732 52.22949399876698, 5.040705999833083 52.22954699935855, 5.040518000844662 52.22983399903477, 5.041345000387359 52.23007699960938, 5.041586000401368 52.23088799901512, 5.042396999367242 52.23054700031276, 5.043243998922854 52.23081899939398, 5.043566998706572 52.2311180001126, 5.043822999438383 52.23101899930155, 5.051814000208486 52.23356299937156, 5.051899999414285 52.2337039995825, 5.052237000056781 52.23385800068672, 5.052455000480808 52.2337649989807, 5.052920999932002 52.23392199963743, 5.053282999882528 52.23424299990671, 5.053531998768719 52.23411799872535, 5.056223998807678 52.2349789991696, 5.055790000510134 52.23521399952111, 5.056280999269357 52.23538099868614, 5.056355000166883 52.23533699958455, 5.056761999438354 52.235592999636, 5.057484999480383 52.23520699914905, 5.057729998930483 52.23535299861413, 5.05750199991623 52.23583599879417, 5.05718899872274 52.23600499907707, 5.056895000515604 52.23653599917819, 5.056275999974243 52.23712700024284, 5.049542000440754 52.24109799988249, 5.048330001357547 52.24199099980111, 5.047641999213774 52.2422159988116, 5.046577999093158 52.2428349999477, 5.045853999192106 52.24338200049402, 5.046520998631479 52.24697200031389, 5.046633999695814 52.24854099994577, 5.046386000668646 52.2489029997633, 5.045292998971448 52.24987799877879, 5.044803000071246 52.25066599878044, 5.043979000105617 52.25244899906239, 5.043921999643938 52.25334699823359, 5.043579999706328 52.25429799941223, 5.043475000346637 52.25533499909196, 5.043278999653571 52.25567099995506, 5.042942998870098 52.25597699962833, 5.042143001185936 52.25645599898728, 5.041380001118076 52.25669799971003, 5.04067600122994 52.25669400030672, 5.040061999983694 52.2564989996541, 5.039436000429175 52.2561249987946, 5.038876999926561 52.2555939986951, 5.03797299990927 52.25422599882263, 5.037494999317341 52.25368899820177, 5.036564000273975 52.25300999893575, 5.035733001295187 52.25265399822344, 5.034919999778807 52.2526819997116, 5.034496999930512 52.25286499932191, 5.032940001332626 52.25408599987919, 5.031860000635185 52.25511699903776, 5.03117099863239 52.25602399969848, 5.030972001194718 52.25681999850603, 5.031197000631903 52.25731399845981, 5.032084000213347 52.25813499920282, 5.032458001304607 52.25870599900021, 5.032671999460081 52.25947199945005, 5.032488999766772 52.26016599931028, 5.03195500140465 52.26087699946699, 5.031100000144393 52.26155699999923, 5.030217999858063 52.26208899853284, 5.027444000049395 52.26349199884753, 5.02658199977598 52.26414800012714, 5.026506999019431 52.26439599853836, 5.026584999353048 52.26463499970833, 5.027455001331107 52.2654339994835, 5.027799000986763 52.26604099957497, 5.027911999218635 52.26689399979205, 5.027749999397265 52.26757400032364, 5.02713600098348 52.26822899891957, 5.025541999104369 52.26906599869066, 5.024816999344296 52.26963299908375, 5.023479001029459 52.27120599952958, 5.022886999514173 52.27248299881084, 5.023021000450447 52.27284799959571, 5.023340000798073 52.27307300002133, 5.02370999962078 52.27320799829399, 5.024077998725442 52.27323599836583, 5.028330999630839 52.27209099922114, 5.029537999418933 52.27201399937761, 5.030578999949565 52.27224799987692, 5.030933001027909 52.27250299865984, 5.031256000811627 52.27317299926634, 5.032687000177883 52.27495599812578, 5.033328000450204 52.27543699860117, 5.034000999043712 52.27571899902119, 5.035079999882131 52.27592799900102, 5.038224001345968 52.27697099919842, 5.042110000173264 52.2778699996332, 5.042909000830864 52.277957999252, 5.043734000655517 52.27790899948035, 5.044804999789292 52.27768899972527, 5.047215999788411 52.27701299859806, 5.047965998856514 52.27694499882824, 5.048601999833721 52.27702999889457, 5.049251998837248 52.27735499856498, 5.049621000633394 52.27779599934222, 5.049725999993084 52.28049999818541, 5.050060001058513 52.28114099886843, 5.050753999523961 52.28181799842981, 5.051594000066415 52.28219499884028, 5.054164000309836 52.28269599916411, 5.055292999764312 52.28300599965561, 5.058906000256411 52.28460799860456, 5.060518999456953 52.2851689998913, 5.061118999844419 52.28521400025959, 5.061856000745228 52.28512999862798, 5.062480000581702 52.284982997897, 5.062838001096137 52.28479799858596, 5.062921000724867 52.28438699899888, 5.062800000788351 52.28257199783452, 5.06306999954648 52.28184099924749, 5.063580001292058 52.28130599974683, 5.06438300138575 52.28074999913009, 5.065145998621148 52.28054499996995, 5.065879999944888 52.28054300026829, 5.065879999944888 52.281270999303, 5.065491000968288 52.28127199915382, 5.065658000084771 52.2826919997608, 5.065472000814395 52.28519799981392, 5.064792000375265 52.28508099885639, 5.064194999564866 52.28629299933387, 5.063284000534416 52.28719499931981, 5.059539998683112 52.28931999955783, 5.054833999326277 52.28754899891231, 5.052768998700859 52.28648499901686, 5.051578999348613 52.28731799938226, 5.050404000714169 52.28787199888054, 5.047483998828495 52.28501999945867, 5.043957000374656 52.28303499957825, 5.042778999330682 52.28248799903533, 5.041625000568176 52.28336899932202, 5.040793998756927 52.28388099942105, 5.039429001416016 52.28324399814162, 5.035148998652082 52.28552699888723, 5.033419998810139 52.28631499888448, 5.033212999667824 52.28625799889007, 5.030527998642023 52.28848699919269, 5.026507998878454 52.28397799911342, 5.024382001187728 52.28135699780387, 5.021866998855479 52.28265299849746, 5.021862999419389 52.28942599932375, 5.021662999290232 52.30122099874574, 5.021542999212739 52.30245699988857, 5.019409999676393 52.3025019988405, 5.017840000078751 52.30266199904801, 5.016684998624761 52.30288099895142, 5.014264999894438 52.30367700058554, 5.014228999304697 52.30358999940171, 5.012555000206387 52.30356699858411, 5.011769000548544 52.30339299904889, 5.011050999801627 52.30302899953281, 5.009915001333992 52.30209400022326, 5.008960999868181 52.30172599847136, 5.007892000452451 52.30152199916272, 5.007544001360705 52.30086799900482, 5.005264999747352 52.29839299858206, 5.003835000240119 52.29850899968852, 5.003233000134606 52.29738899965142, 5.00322700098047 52.29606499888907, 5.002484000925525 52.29603499911567, 5.001850999525386 52.29587899972752, 5.001788999768594 52.29279299981068, 4.998813999971748 52.29023899983981, 4.998551000226778 52.29023399916947, 4.998452000021222 52.28983900002838, 4.998297999072033 52.28981499794369, 4.997474998965426 52.28911799853421, 4.994116999346117 52.28928799866653, 4.990458999533073 52.28960299982806, 4.983505999716535 52.29036799900716, 4.98288899889322 52.28964199825892, 4.975147999700793 52.28634199910543, 4.973249001165297 52.2852359998102, 4.973072000626125 52.28502200057654, 4.972611000470044 52.28484599992294, 4.969725999315088 52.28312099949532, 4.969378000223341 52.28178899850717, 4.968815000284637 52.28097299843756, 4.968753000527845 52.28072599987781, 4.968328000961504 52.28041199856666, 4.967673999689426 52.27958199916906, 4.967575999342894 52.27956099946925, 4.966842000851615 52.27987799891671, 4.96649099935034 52.28009599897005, 4.966165999848577 52.28097999880956, 4.965708999128586 52.28057799929589, 4.965316000715895 52.28044799886131, 4.962548000061364 52.28028499910076, 4.962244000431539 52.2802069994065, 4.961054001079294 52.27817399960377, 4.959968001227717 52.27834599943802, 4.958664000952114 52.27834299846933, 4.957808999691856 52.27861199941224, 4.956773001288799 52.27867099910838, 4.953797998659492 52.27802699887262, 4.952979000821438 52.27822099825752, 4.951500999583731 52.27826399892421, 4.94991399955024 52.27844299913047, 4.949011999250994 52.27862700000709, 4.947979000425005 52.27895299952826, 4.9472140006391 52.27900399900155, 4.946374000096645 52.27896899997393, 4.945534999413214 52.27882699991316, 4.944112998778163 52.27828200048776, 4.942692000834596 52.27818399811203, 4.942264998717748 52.2783040000384, 4.940716998851067 52.27905699817649, 4.940226000091844 52.27916600032751, 4.939309998933817 52.27918099950613, 4.938638000199331 52.27941399873814, 4.937462998732426 52.27954799857606, 4.936348999995751 52.28016999926103, 4.933800999483291 52.28089299904164, 4.932757999234614 52.28067099816878, 4.931387999766127 52.28050600012281, 4.928587000931385 52.27961099909171, 4.927467000208113 52.27959599849686, 4.926444999831373 52.27977999937345, 4.926121000188633 52.27910799906599, 4.92595800050824 52.27819499930358, 4.926785000050938 52.27685599794239, 4.926753998756311 52.27537699905418, 4.927256998656269 52.27462199838157, 4.927661001183132 52.27371199958721, 4.927459001335931 52.27331099992391, 4.92666699969149 52.27263899961578, 4.926650999114665 52.27244499881449, 4.926991999193253 52.27216999876634, 4.928014999429015 52.27174200029813, 4.928118998929683 52.27114399886468, 4.9285060010207 52.27046599803511, 4.929354000435336 52.26989099883547, 4.929655000488093 52.26924599874801, 4.929421999346264 52.26840299845575, 4.928317999199817 52.2679819989431, 4.925818000417832 52.26751699891285, 4.92474800114308 52.26700099940918, 4.924234999820433 52.2660839988255, 4.923842001407742 52.26374499934537, 4.923563001085947 52.26331799931121, 4.923256998905615 52.26308999933277, 4.922130999028207 52.26262999855631, 4.920503998968886 52.26239000036788, 4.91894300093491 52.26204599928226, 4.918118001110258 52.26169500065706, 4.917986999751053 52.26151499918327, 4.917986999751053 52.26121099921191, 4.918604000574367 52.25938499826562, 4.918637998613601 52.25894199920167, 4.91840700002228 52.2582410003855, 4.917601000351521 52.25707699982366, 4.916630001282323 52.25638799921725, 4.915526001135876 52.25597199895796, 4.914309999784116 52.25534499901649, 4.913364999881971 52.25468499833265, 4.912869998854195 52.25413099883147, 4.910805001061239 52.25261399852531, 4.910242001122535 52.25246199853942, 4.909714998941109 52.2524459995099, 4.909083000232454 52.25251999980118, 4.907987998817211 52.25283299843043, 4.907159999415491 52.25289700021339, 4.905711999613388 52.25316399862329, 4.904360000439771 52.25324599913742, 4.903076000177084 52.25320499958846, 4.902584998585398 52.25311299914979, 4.901794999491464 52.25275499873583, 4.899819000481374 52.2512369985785, 4.898900999605303 52.25096199994565, 4.898569001090381 52.25093199875582, 4.89635199923382 52.25129199887155, 4.895656001050328 52.25160499891707, 4.895226999215435 52.25203699962239, 4.893686001194375 52.25265999874465, 4.893042001344986 52.25300199871288, 4.891325999670336 52.25330299913213, 4.890741000000672 52.25310499892692, 4.889417999571175 52.25194799873619, 4.888814999606639 52.25163899951023, 4.888265000667692 52.25119599902953, 4.887356001355287 52.25092699808545, 4.887017000994745 52.25089899942972, 4.886512001376742 52.25100599904712, 4.885712000860119 52.25147099907858, 4.884842998741084 52.25261599964319, 4.884083001082753 52.25335199890396, 4.883409999656783 52.25365399917402, 4.882719000768403 52.25371199901955, 4.877728998962198 52.25281599955008, 4.875878999183737 52.25188499963649, 4.874524000433054 52.25102199949287, 4.873852998725128 52.25074299862425, 4.87270199953969 52.2509669991998, 4.872290000973106 52.25118199970166, 4.870037001359266 52.25336099897766, 4.868958000520848 52.25189799911352, 4.868582999570566 52.25157899996302, 4.867859999528537 52.25139299938396, 4.866877999177628 52.25125299902425, 4.866252999482132 52.25106099934018, 4.865706000120253 52.25076399832415, 4.864756000922993 52.24998099899289, 4.864400999985627 52.24979499983, 4.860621000377044 52.24821700012473, 4.859265998793898 52.24779299964202, 4.857522001066615 52.24772300017023, 4.857368999976448 52.24761499928573, 4.857148999834378 52.24734599975772, 4.8568620006404 52.24639099917471, 4.855769998802225 52.24604799935529, 4.855486999044339 52.24585899922356, 4.855311001196651 52.24560499887419, 4.855183999273537 52.24277499898418, 4.854937999964414 52.24214599933975, 4.854650000911414 52.24196900025034, 4.854215999781408 52.24187299899196, 4.851669998986994 52.24203799845514, 4.851444999549809 52.24199999987482, 4.851254000984319 52.24186699847073, 4.851200999958731 52.24153899924601, 4.851305999318422 52.24128199934396, 4.851624999666049 52.24062699932906, 4.85218699974573 52.23988399969476, 4.852028999360451 52.23939500041002, 4.85121100138142 52.2387030002492, 4.850792000969215 52.23860699899076, 4.849925001400686 52.23892899911063, 4.84814100081511 52.23976199948224, 4.847550999017869 52.23982699969995, 4.847280000400717 52.23976499903474, 4.846997000642831 52.23956600039449, 4.846717000462013 52.23902499895284, 4.846547998795022 52.2382579986497, 4.846344999088798 52.23791999950024, 4.846083999061873 52.23771899974205, 4.845480999097338 52.23740999909918, 4.843644000318634 52.23712499912496, 4.842939000571476 52.23673899863809, 4.842723999724518 52.23626599979889, 4.842925999571719 52.23549099927252, 4.842844999661034 52.23534699950912, 4.841271000627302 52.23517199870498, 4.839222000578708 52.23545299927595, 4.838328999010666 52.23525899988974, 4.836118998999726 52.23394299933739, 4.834887999762626 52.23287999928383, 4.834446999619461 52.23274099877455, 4.833225998972587 52.23264999960262, 4.832533000366163 52.23240999858063, 4.83191299996578 52.23205999838839, 4.830972999358748 52.23130799867938, 4.830106999649242 52.23091299953464, 4.829421000055977 52.23071999999913, 4.828035000010665 52.23049399972118, 4.82656100104151 52.23007799946021, 4.823710000758249 52.22981099963313, 4.822421001200448 52.22931999922972, 4.820806999308422 52.22883299964585, 4.81913999922327 52.22894099911445, 4.8171929989573 52.22832500036195, 4.815768998604204 52.22759599863792, 4.814454999738373 52.22722399889461, 4.813513999272319 52.22713699912595, 4.810054999729408 52.22710299994889, 4.809158998584298 52.22696000003614, 4.80881399906962 52.22697099981155, 4.807268998780007 52.22732999866157, 4.806026001234635 52.22794699868123, 4.805404001116207 52.22798899949741, 4.803201999977448 52.22743499999392, 4.801755000034367 52.22688199892499, 4.797676999950097 52.22666799827316, 4.795441000772104 52.22665399894525, 4.793520999532209 52.22689899922165, 4.79120300058451 52.22737499903026, 4.789313000780218 52.22788999868631, 4.788623998777423 52.22791699890751, 4.787646000695066 52.22782300018302, 4.786785000280673 52.22789999861087, 4.78625800093171 52.22807399956438, 4.785656000826197 52.22845899878471, 4.784936000361236 52.22862599794993, 4.783928000843276 52.22873999935599, 4.78098599922664 52.22874099920682, 4.779320998859534 52.22808799889229, 4.776739000307842 52.2275039996151, 4.7763999999473 52.22737299932859, 4.776152000920131 52.22713199987178, 4.775531000660726 52.22571999947534, 4.774603001194427 52.22454700025005, 4.772078000271951 52.22302299956478, 4.7699579989029 52.22228499918261, 4.767307998607817 52.22115399935671, 4.766894000323187 52.22085099781805, 4.766244001319662 52.22059199962971, 4.763132001009476 52.21859499868675, 4.762074999902017 52.21813000006912, 4.75984799945523 52.21758699892398, 4.75910299968224 52.21753799915187, 4.75755899925165 52.21762199936815, 4.753773000488931 52.21754099870439, 4.753298999333094 52.21746799967976, 4.752649000329568 52.21722099828511, 4.750668999050927 52.21572099968304, 4.749271000697343 52.21533499919488, 4.747971999716853 52.21486799945912, 4.747441000931799 52.21478799864616, 4.745743999411042 52.2148369998345, 4.744802998944987 52.21475299961818, 4.743942001363057 52.21482600005907, 4.743478998656469 52.21472499954609, 4.741335000670873 52.21240899944883, 4.739409000276842 52.21113899911141, 4.738016001218371 52.2099479983197, 4.735377999231561 52.20741499943193, 4.735616999527525 52.20731399891883, 4.73499999870421 52.20676699836941, 4.734782001112646 52.20686299962833, 4.734339001251435 52.20651599898721, 4.734039001057702 52.20667099852672, 4.732834000987653 52.20697199894815, 4.732584999269001 52.2071159987122, 4.729245999803584 52.20955799984405, 4.72876599949361 52.20981900056695, 4.72924499994456 52.21020899904258, 4.729456001355429 52.21005399950317, 4.729703000523574 52.210213999713, 4.727603999026461 52.21110599836889, 4.727298999537614 52.21113099888849, 4.725445000323063 52.21209100014719, 4.724461000254109 52.21275099941922, 4.724486999421161 52.21286499940934, 4.724297000714694 52.21305299827498, 4.724668999255446 52.21329299929772, 4.724275000983733 52.21355199890257, 4.724488999139206 52.21414900049058, 4.7244649996902 52.21444499882504, 4.724402999933408 52.21458699888724, 4.72385300099446 52.21495099840837, 4.723677000314311 52.21517600025224, 4.723763999379132 52.21657399990686, 4.723301999364028 52.21635399873344, 4.72368399932747 52.21812800036744, 4.722896999810603 52.21982499932435, 4.722923998836677 52.2209769988505, 4.724320000304678 52.23196300011146, 4.724155000906239 52.2323709987332, 4.723235000312123 52.2323629985103, 4.720909999518803 52.23252099901789, 4.704875999644493 52.23381399875269, 4.703713999177344 52.23378899964939, 4.69822399988071 52.23265500027301, 4.696494000179744 52.23221599919433, 4.687309000621304 52.22710499965056, 4.686966000824671 52.22709100032264, 4.686150999590246 52.22660999984361, 4.685063000020623 52.2264449989638, 4.682879999035757 52.22635599949346, 4.68284800071457 52.22649999925707, 4.679975000700347 52.22676299968094, 4.679754000699253 52.22654799917827, 4.678149000370893 52.22637399822472, 4.677058001224202 52.22612299884329, 4.676699000850745 52.2259259999043, 4.673322001077542 52.22871399898562, 4.670348001139718 52.23075600029407, 4.669663001405476 52.23035999846602, 4.669617999252069 52.23039799987885, 4.665915999977104 52.2280369994186, 4.665045000972484 52.22757399908711, 4.664155998840533 52.22730299985658, 4.659010999058578 52.22624700017398, 4.657669001307651 52.22582999864567, 4.654098000559428 52.22445899921431, 4.651248000135189 52.22295399994355, 4.646609999689282 52.22096999847843, 4.645567999299628 52.22075899879512, 4.644230000984791 52.2206049991068, 4.643253999788018 52.22030899935641, 4.632937001198034 52.21626999851715, 4.63123700010021 52.21593999817321, 4.619638000965583 52.21457799881348, 4.618150001137648 52.21446799964296, 4.606753998876783 52.21458099978221, 4.602762000830774 52.2145579989643, 4.601707999300384 52.21465099925437, 4.59746300009963 52.2157270002043, 4.584354001406108 52.21876499882095, 4.583553001030462 52.21887399955678, 4.581731999996122 52.21879400016009, 4.573409000711097 52.21716699925881, 4.571396001252245 52.21695899912789, 4.570221999644362 52.21702799874918, 4.558432998970806 52.21879899941428, 4.557106998964242 52.21906199842225, 4.55672099956471 52.21920699945307, 4.556399999499037 52.21947700024932, 4.550816999150985 52.2255620003838, 4.550522001084827 52.22610199914331, 4.550469999918262 52.22674499953347, 4.550767000534927 52.22742799820563, 4.552156000157306 52.22918100013658, 4.553624999831348 52.23093299938377, 4.554207999782967 52.23139699814964, 4.555583001379029 52.2320109986164, 4.560452000416257 52.2329719997228, 4.56111899985563 52.23317700030069, 4.562272998618135 52.23384300009177, 4.562661000568174 52.23424599945921, 4.562937001312902 52.2347579995622, 4.567550999477341 52.24921599839273, 4.569520999333294 52.25969099935494, 4.569492000589173 52.2603149997435, 4.567868999965944 52.2672939981887, 4.56797199960759 52.2680719996799, 4.568335999276162 52.26855299873959, 4.568978999266528 52.2690189986206, 4.587942999616739 52.28009899993877, 4.58844000036256 52.28050199930328, 4.588849999211098 52.28105999962169, 4.591173000286373 52.28572800006013, 4.5960480013102 52.29136899838644, 4.596245998888848 52.29198999921942, 4.596375000530007 52.29417500041886, 4.597086999290324 52.2960840003034, 4.597476001099387 52.29667599838091, 4.604446001351773 52.30598699871229, 4.611107999705782 52.3116070001534, 4.611623000746473 52.31249900021133, 4.611675998939599 52.31355999913375, 4.606313998592642 52.31491199996413, 4.60305599903791 52.31583199867704, 4.602758001394683 52.31604399962424, 4.6019729987634 52.31625700042225, 4.601786999493024 52.31640399832007, 4.601316000746715 52.3165709988992, 4.598976999094615 52.31724899972385, 4.596540999787468 52.31772399967491, 4.596123999093309 52.317779998402, 4.595486001230517 52.31770599952769, 4.594389999956252 52.31772099870624, 4.592259999996974 52.31784300033349, 4.5909809990294 52.31778999832643, 4.588853998647189 52.31795499920388, 4.587685999025904 52.31788099891335, 4.587752001051249 52.31707399892196, 4.587971001334298 52.31705599877474, 4.588017000514265 52.31696599945485, 4.587485998896748 52.31676199873046, 4.587009000996304 52.31699099997404, 4.58660800087897 52.31700499930175, 4.58609000026121 52.31731499979159, 4.585865000824025 52.31764299901242, 4.586219998928931 52.31789999891139, 4.585792999644545 52.3181969999243, 4.583273000849644 52.31839499871126, 4.573240998903129 52.31535599887501, 4.573036999337883 52.31550699900858, 4.568954999817521 52.31651299905425, 4.566823999999221 52.3128689988318, 4.566559000536205 52.31273199944212, 4.566228998906867 52.31236599880871, 4.56370700039392 52.30929799904708, 4.558207999533622 52.31080299971368, 4.557670998761969 52.31105799849507, 4.554071999128649 52.3119879999654, 4.554004000217721 52.31192399818304, 4.551476999577199 52.3127939986904, 4.547355000031007 52.31398799901537, 4.530349001228477 52.31839199915881, 4.493856999189424 52.32822799903082, 4.494714000167725 52.32922899982037, 4.498492999917286 52.33536099991969, 4.50202899993479 52.3403699990149, 4.503801999238656 52.34316299871649, 4.506230999532645 52.34662299804697, 4.508376000209726 52.34986899956031, 4.509249998791413 52.35143199864576, 4.510453999002438 52.35274899902468, 4.512111000497363 52.35501899885238, 4.513342999593485 52.35768199953076, 4.514967999934761 52.36046700041826, 4.517266998728568 52.3634509985247, 4.519002000557109 52.36637999917011, 4.521103998798829 52.36941899898204, 4.522456000804906 52.37182399990682, 4.522991998885074 52.37262799892262, 4.523668999747136 52.37335299839439, 4.524690000264853 52.37482199875176, 4.524956999445914 52.37545599905339, 4.52632299947831 52.37721099922906, 4.526559000197206 52.37782499826557, 4.526982000045502 52.37833999932568, 4.526876000826787 52.37849699856262, 4.527136000994689 52.37891599978257, 4.527310998983355 52.37894899910801, 4.527412998765978 52.3788959999339, 4.527840000882826 52.37971999879748, 4.529907001226289 52.38264399876516, 4.529953000406256 52.3829069991826, 4.530767998808219 52.38402399965225, 4.531077000565619 52.38481999986004, 4.531475001105886 52.38544699978878, 4.533284000999492 52.38793599950532, 4.53679299915846 52.39344699872517, 4.537733999624515 52.39473599902409, 4.539236000311229 52.39745699953013, 4.54047400139395 52.39915699940796, 4.541413999168519 52.40079099921846, 4.543485998807096 52.40372499910077, 4.544439000413885 52.40540999838239, 4.545117001134969 52.40616899985897, 4.546478998898812 52.40850999898849, 4.551351000345569 52.41617100032133, 4.553135000931147 52.41871199934887, 4.553826999678549 52.42073299948093, 4.554568999874471 52.42246999949722, 4.555276999198697 52.42359600003341, 4.556249000959379 52.4254899992847, 4.556959999860673 52.42719300012369, 4.557765999531433 52.42864199920393, 4.560531000608896 52.43649099937166, 4.560923999021587 52.43859899926385, 4.561678000358243 52.44029300002591, 4.561281999536022 52.44149099973022, 4.56145000134399 52.44219899889809, 4.561359999869639 52.44278699897505, 4.561414000754249 52.44352900015112, 4.561810998603031 52.44412799858679, 4.561565999152931 52.44446699899234, 4.561733001101876 52.44528600000967, 4.561558000280749 52.44612299975753, 4.561278000099931 52.44653699888659, 4.560659999417593 52.4465739990311, 4.560494000160134 52.44665000043796, 4.560308000889757 52.44702200016847, 4.560233999992231 52.44747599899438, 4.560030000426984 52.44783900006765, 4.559396999026845 52.45146500006612, 4.559225000615248 52.45193599918777, 4.55808499987906 52.45406899959352, 4.556971001142385 52.45673999903694, 4.556217999664752 52.45813899848941, 4.555433999724954 52.45923099984347, 4.555106000646123 52.45993200005378, 4.554891999658188 52.46004799832486, 4.554153998898357 52.46016799883158, 4.553494001304605 52.46043500006523, 4.552934000942969 52.46098999939929, 4.540325999598897 52.45810099849416, 4.539281999491196 52.457972999181, 4.538003001356083 52.45806399976593, 4.53732900007109 52.45823399989374, 4.53636799959212 52.45867299812443, 4.534688001339672 52.4598789980479, 4.533567000757378 52.46093299940645, 4.532541000944548 52.46227899826835, 4.532042000480681 52.46345299871692, 4.53207800107042 52.46383699807227, 4.532292999084917 52.4639259989555, 4.532531999380881 52.46388800037651, 4.532632999304482 52.46349199856294, 4.532576998701826 52.46305099921477, 4.532730999651014 52.46253999898091, 4.533539998898842 52.46137799957418, 4.534801999430568 52.46012699928395, 4.536786000145302 52.4587070001327, 4.537700998611843 52.4583660000263, 4.538830000898781 52.45821499989612, 4.539530001350826 52.45823899914773, 4.540490998997335 52.45838799957632, 4.554422000770902 52.4615669982833, 4.55455799859276 52.46162299984155, 4.554787000298496 52.46199799912359, 4.554551999438623 52.46226399909015, 4.554585000451295 52.46236499959921, 4.555305000916256 52.46252699950431, 4.555651000289958 52.46248899950908, 4.556195999933792 52.46225999827073, 4.55560500110999 52.46235799922741, 4.555264001031402 52.46234300004922, 4.555223001146548 52.46213099910688, 4.555474999609808 52.46197599957359, 4.558562000611966 52.4610259982767, 4.558968999883436 52.46145699911703, 4.559094999115065 52.46148599903886, 4.559120001255556 52.46140099897499, 4.558691999279686 52.4609859999961, 4.558965000447345 52.46090299821761, 4.558915998857848 52.46085599814896, 4.559470000065348 52.46067399839541, 4.559522001231914 52.46073199823906, 4.562282000181801 52.45988400013434, 4.563406000341164 52.46079299905133, 4.564379998987429 52.46173899952883, 4.564319998948683 52.46233000057261, 4.563255998828066 52.4623499990048, 4.56324999967393 52.46263799993757, 4.568732999957405 52.46261199815193, 4.569201998985667 52.4625020004019, 4.569455999999434 52.46225999827073, 4.56762999966998 52.45964799852408, 4.569247001139074 52.45922299962086, 4.571360000662504 52.46220099857631, 4.572541001283546 52.46259899867533, 4.574160999497246 52.46259399942134, 4.574240999548908 52.46253899913011, 4.577049000229271 52.46273699932882, 4.577082001241942 52.46286199908948, 4.577386000871767 52.46273799917962, 4.578549001197938 52.46286899946129, 4.578815000519977 52.46265699993518, 4.578791001070972 52.46243399921785, 4.577014999357575 52.46158499843011, 4.579341000009919 52.45990799938595, 4.579220999932425 52.45973499970579, 4.5795570007159 52.45945399914469, 4.579223999509493 52.45888299936529, 4.577921998951935 52.45827199847276, 4.578058999465276 52.45818999937748, 4.579655001062432 52.45894999928236, 4.579700000383376 52.45891099943632, 4.578014000144332 52.45810599916437, 4.578110000772819 52.4580339999933, 4.579486999254463 52.45868899998587, 4.579834001319649 52.45857099918073, 4.58007900076975 52.45857899940334, 4.580144999962632 52.45848399799901, 4.58033800107863 52.45850600038147, 4.580582000669708 52.45874899953114, 4.586445001198578 52.45654299868884, 4.58721600013862 52.45724499875025, 4.581706000829071 52.45935699803878, 4.581815999483875 52.45950099921334, 4.580530999362164 52.4610259982767, 4.580580000951661 52.46111800012859, 4.580408999566625 52.46130899995553, 4.581553999597928 52.46245399906626, 4.593690999363229 52.45785699949367, 4.593605000157431 52.45777799853455, 4.594292999468742 52.45766499981583, 4.595193999908965 52.45774599906031, 4.594404000815031 52.45801099917625, 4.594454999290112 52.45805699939412, 4.595298999268657 52.45775699883533, 4.595836000040309 52.4579719993302, 4.594376998956495 52.45852099955968, 4.595898999656124 52.45801199902706, 4.596213000708637 52.4583660000263, 4.588020000091333 52.46124899899407, 4.588178000476612 52.46141299860079, 4.584482000355782 52.46273899903041, 4.584358000842199 52.46262699874635, 4.584247999354933 52.46266199918918, 4.584118000687212 52.46286899946129, 4.583604999364566 52.46306099913897, 4.583229001387722 52.46284999946367, 4.582970001078842 52.46287099916288, 4.582498999500073 52.46308799935919, 4.582572000538576 52.46330199858686, 4.582802999129897 52.46337899984432, 4.590325000871738 52.46364899921401, 4.590452999821412 52.46374499905285, 4.590444001090209 52.46449799858526, 4.59033699918001 52.46466699886211, 4.590175999217663 52.4646069993169, 4.589344000379851 52.46468099818949, 4.588936001249358 52.4645959995419, 4.589970999793394 52.46451999813526, 4.588949999275677 52.46451099806185, 4.58887900078768 52.46457899924589, 4.588300000272151 52.46449099962967, 4.586986001406321 52.46445299963446, 4.586811000585194 52.46452299910387, 4.586848001033957 52.46470600012432, 4.587116999933064 52.46483199973574, 4.588294001118015 52.46502699896577, 4.590315999308071 52.46524599886359, 4.592393001074224 52.46531399863139, 4.596475000594586 52.46531399863139, 4.596866999148253 52.46550199890581, 4.59681500081415 52.46573399969644, 4.597059000405228 52.46571799925123, 4.597054001110115 52.46597599899503, 4.5968259992634 52.46595099989265, 4.596899000301903 52.46617199949205, 4.596478000171653 52.46640599856802, 4.591507001351802 52.46640699841881, 4.591270000773884 52.46659500010941, 4.591206001299046 52.46698999923956, 4.591460999339374 52.46729700016972, 4.591645998750726 52.46733299904709, 4.600425998755742 52.46679699971119, 4.601568999068998 52.46699799946216, 4.604683998956252 52.46697100065818, 4.605058000047512 52.46710099825653, 4.605126998817463 52.46721299854052, 4.60473300054575 52.46724299972932, 4.604776000148648 52.46748099962466, 4.605678000447895 52.46746399932866, 4.605741999922733 52.46790099927335, 4.604860999495425 52.46798699918792, 4.604901999380278 52.46823999826142, 4.605184999138165 52.46822699878484, 4.605003999162902 52.46844099942857, 4.601917000993206 52.468822999082, 4.600859000026725 52.46916699874024, 4.592214000817006 52.46969999993976, 4.592077000303665 52.46975699993253, 4.5921689986636 52.46989600043666, 4.592649998832597 52.47000499833577, 4.594283000878515 52.47020699793742, 4.601360000208638 52.4697939986607, 4.601586999363867 52.47062299959896, 4.601524999607076 52.47072899936187, 4.600111000676668 52.47092400000794, 4.600117999689827 52.47097499947969, 4.601443999696391 52.47102499910065, 4.601491998594403 52.47148499986208, 4.600718000077293 52.47164599991613, 4.600738000090209 52.47175699893302, 4.600103998831047 52.471790999525, 4.599596999494999 52.47172899886204, 4.597812998909422 52.47122099818127, 4.597187999213926 52.47117599923052, 4.597170998778077 52.47106299909581, 4.59662799885229 52.47108400021121, 4.596639999993023 52.47121099825709, 4.594828000522349 52.47132499965881, 4.596194000554744 52.47173699908464, 4.596080999490409 52.4718969992879, 4.597123999739088 52.472195998579, 4.597247999252672 52.47204399859835, 4.59838799998886 52.47236199930325, 4.598809999978132 52.4724109990734, 4.603789000502626 52.4721159991855, 4.603846000964305 52.4725049992105, 4.607552999534384 52.47228499946213, 4.608879999399971 52.47212699896047, 4.609102999119111 52.47295099922823, 4.602775998857091 52.47374399845631, 4.602795998870007 52.47388299896035, 4.602648999766439 52.47390599977733, 4.602640001035235 52.47376999882568, 4.60239399889365 52.47379799889665, 4.602486000086046 52.47392499835869, 4.599754000021255 52.47426999928366, 4.598861001285675 52.47401899991198, 4.598430999591759 52.47407399878691, 4.598264000475275 52.47402999968698, 4.598271999347457 52.47392899917809, 4.597866999794032 52.47386600008058, 4.59798100071739 52.47363499914105, 4.597686999677792 52.47355299862974, 4.59759299876735 52.473673998987, 4.596189001259631 52.47328499896205, 4.595889001065897 52.47339099872493, 4.595564998590696 52.47381899859582, 4.586387000877876 52.47167299872009, 4.58610099871046 52.47207199866933, 4.585886000695965 52.47204799941776, 4.585611999669283 52.47222799946939, 4.584844000306308 52.47210399955971, 4.583996000891673 52.47189299846849, 4.583846999237598 52.47178199945161, 4.584352998714623 52.47152499814262, 4.58428300008565 52.4714569997911, 4.583773001172533 52.47170799916287, 4.582416999730365 52.47080099994903, 4.58247999934618 52.47065899847631, 4.582365001396262 52.47054499849079, 4.579645999498766 52.46988999991566, 4.578863999277013 52.46953499906625, 4.574289001279382 52.46881399900861, 4.57312900053028 52.4688499993022, 4.567407999809863 52.46770399892561, 4.557694001184414 52.46784700024919, 4.55693700027069 52.46766299937802, 4.553857001114153 52.4665109984802, 4.553795001357361 52.4667429992708, 4.556472000678518 52.46769799982082, 4.55664199937207 52.46780799898698, 4.556606998641352 52.46799199985814, 4.549805000196934 52.46909699927087, 4.548572001241789 52.46921999932982, 4.547406001338549 52.46913899866925, 4.54372099966697 52.4683069981784, 4.543084998689762 52.46803199955491, 4.542831000508457 52.46740599948508, 4.542372999929444 52.46735199904469, 4.542225000966853 52.46748499902787, 4.542429000532101 52.46795699799912, 4.542975000034957 52.46833100026242, 4.547188000914524 52.46934099827094, 4.547995000444305 52.46944199877989, 4.549597001195597 52.46935299931295, 4.557851998737231 52.46805799850809, 4.564933000335907 52.46786199942736, 4.565292000709364 52.46795299859593, 4.565490001120475 52.46806699858148, 4.565644999096224 52.46876599908923, 4.565999000174569 52.46949799892187, 4.56702899942349 52.47081299957482, 4.567326999899178 52.47112799931116, 4.568015999069512 52.47150699941205, 4.569244998588566 52.47244799921776, 4.569874000552615 52.47318599957098, 4.571220000572096 52.47432599800937, 4.572446000514081 52.47577400006054, 4.573892000598139 52.47715099995888, 4.575300000374411 52.4788819994388, 4.57712800042191 52.48166199960318, 4.58147599926431 52.48739499819148, 4.584424999894104 52.49302799894929, 4.58535599893747 52.49534799976406, 4.586570000571184 52.49740999941923, 4.587157999817918 52.49883599908284, 4.587760999782454 52.49970999896787, 4.58915600139143 52.50286599966982, 4.589659001291388 52.50432500007322, 4.591179999299532 52.50729399893426, 4.592947999308285 52.51297399974275, 4.593450999208242 52.51397599893788, 4.595076999408541 52.51909899929368, 4.595405001319833 52.51983899934308, 4.596543999364537 52.52364899875396, 4.597049998841562 52.5257419994393, 4.598215998744801 52.52845899907006, 4.598851999722009 52.53107299849012, 4.653076000182688 52.53068899913883, 4.656199998801148 52.53152899984406, 4.659883000754682 52.53176299891764, 4.667241000124646 52.5307909994976, 4.669073999467258 52.53063099929582, 4.671909999032717 52.53017099853866, 4.672725000267143 52.53011699951502, 4.678219998858889 52.52934199902435, 4.6789290008746 52.52917799941938, 4.68574600003682 52.52833799871385, 4.69016899963577 52.52745199920705, 4.693096000534602 52.52711199895511, 4.69453499877304 52.52702399933976, 4.695735999406997 52.52672799960359, 4.696191000408942 52.52772000028867, 4.69604299861389 52.5282320003681, 4.69632700106326 52.52870399791865, 4.696652000565023 52.52892199937972, 4.697613001043994 52.52921299844562, 4.695596999175611 52.53587399872733, 4.694844000530439 52.54277599844696, 4.694532999054994 52.54418800018942, 4.695824001163302 52.54425999935952, 4.696274999896695 52.54421999966322, 4.69886100071694 52.54475599899262, 4.698967999794677 52.54489999874902, 4.700344001249761 52.54506699932217, 4.700752000380255 52.54491700046104, 4.70176199961626 52.54507599939546, 4.701932001142273 52.54502099910493, 4.703692999305405 52.54539599979829, 4.703927000306257 52.54556300037142, 4.704086000550558 52.54597099897309, 4.704377999039648 52.54624099833929, 4.705871000995158 52.54620799901471, 4.706431001356794 52.54629500019524, 4.706287998856855 52.54692499824424, 4.705890001149051 52.54746499980899, 4.705898000021232 52.54822299858545, 4.706072000983337 52.54819099911167, 4.710137999926873 52.5490149993696, 4.712336998656102 52.5493049985838, 4.713464001224995 52.54963299920892, 4.715561000171601 52.55074599883002, 4.716250999200957 52.55075999957345, 4.716411999163304 52.55064899914171, 4.716504000355701 52.55068799898719, 4.716394998727457 52.55084499963612, 4.71703299942271 52.55114099937116, 4.718859999611187 52.55164299952465, 4.719804999513332 52.54385499889388, 4.720855998634192 52.54420599891979, 4.720849999480055 52.54427299883596, 4.721788000369042 52.54474900003711, 4.722981999157379 52.54589999823757, 4.723505998929275 52.54667199917387, 4.724236000816925 52.54662899992523, 4.724328999035881 52.54673899909001, 4.725409999592346 52.54642099980504, 4.725453999054267 52.54655199866878, 4.727364998730497 52.54593400024537, 4.727648001320844 52.54594500002023, 4.728580000223234 52.54557199902848, 4.728910998879132 52.54553899828769, 4.729457001214451 52.54518899952886, 4.729566999869256 52.545401998903, 4.729784000434259 52.54548899866732, 4.729983000704392 52.54541399994487, 4.730188000128662 52.54547099852073, 4.730325000642003 52.5454269994213, 4.730250999744475 52.54537799823549, 4.730394999270976 52.54519999930372, 4.730635999284985 52.54514699871477, 4.731270000544148 52.54521299878015, 4.733110998758942 52.54425799965795, 4.735390000372295 52.54382299800385, 4.74342100116823 52.54102599898699, 4.755276001034668 52.53573599949164, 4.759476000914477 52.53737899934046, 4.761318998847317 52.53753999939286, 4.762266001299969 52.5375279997672, 4.763575000870686 52.53729899853155, 4.764283000194913 52.53700799946601, 4.764949999634286 52.53661799817792, 4.766817999707616 52.53517799919552, 4.768019000341573 52.53463499949359, 4.768772998845767 52.53449099832072, 4.770452000071653 52.53436699841225, 4.773344000239769 52.53457400009825, 4.774695999413384 52.53454899816369, 4.776071001009446 52.53428199976558, 4.777732998967022 52.5337449991683, 4.777777001261406 52.53211899819816, 4.778145000366068 52.53087199874116, 4.778799998664707 52.52928599888291, 4.778915999306109 52.52923199985926, 4.779019998806778 52.52869299814376, 4.782476998631643 52.51958099818523, 4.788373000173186 52.51916399950854, 4.790258000682364 52.51924199919988, 4.791796998985379 52.51948099894408, 4.792439998975745 52.51972499935844, 4.793826998880078 52.52072499885092, 4.797564998744784 52.52397299856102, 4.798220999734908 52.52433399851213, 4.799259000688473 52.52458399944725, 4.803178000528441 52.52496199969417, 4.804088999558892 52.52511599937513, 4.805308000487719 52.52565299997314, 4.810633000385915 52.52959199995928, 4.811626999045096 52.53019099838686, 4.813108999718894 52.53066099906812, 4.813645000631523 52.53075699890594, 4.818438998912202 52.53098400044026, 4.820186998908039 52.5312319988411, 4.820904999654955 52.53150499917651, 4.821582000517016 52.53187699890207, 4.824451001095148 52.53383499848513, 4.816992998828145 52.53564799987641, 4.81902800085042 52.53872599945324, 4.820986999424662 52.5382459988485, 4.824461999544397 52.54268900009883, 4.828032000433597 52.5416689993454, 4.831430000078738 52.53987699907101, 4.832169000697592 52.53955199941397, 4.833459999973439 52.53923200042707, 4.833909998847808 52.53895999852656, 4.835490999727162 52.54066199948447, 4.835914999434479 52.54095799921999, 4.838464999664984 52.54205999906775, 4.842278000286239 52.54349299909308, 4.844172999385643 52.54695499943269, 4.84837199940643 52.55013699906409, 4.851827999372272 52.5548319981092, 4.854128000857564 52.55699199870035, 4.855539000210904 52.55772499837364, 4.857425000579104 52.55887599940421, 4.858711000559838 52.55952500028173, 4.859196000164925 52.55996199880389, 4.860240000272626 52.56131899883594, 4.861448999778764 52.56240799920378, 4.862202001256398 52.56348400009504, 4.862939999183767 52.56504699912814, 4.863014000081293 52.56859299966769, 4.863339999442078 52.56962200049007, 4.863952000970279 52.57115999900347, 4.865626999927613 52.57370499878859, 4.868744999391936 52.57600499972595, 4.872236999947519 52.57708399875103, 4.872634000628763 52.57712399844713, 4.87417700120033 52.57701599898441, 4.874912999409654 52.57720299940476, 4.876558999622867 52.57792799885327, 4.880215999576888 52.58030900044827, 4.881163999056101 52.58176499986539, 4.883224000386406 52.5834269996996, 4.88485999917693 52.58432599867497, 4.886444999492374 52.58626799921325, 4.886822000160702 52.58700799925485, 4.887896998730568 52.5905479992613, 4.890011000945483 52.59418799992199, 4.891221000310646 52.59514399888748, 4.89227399914955 52.59611999911704, 4.893593000142955 52.5979619989956, 4.894243999005504 52.59833799953662, 4.895221999920322 52.59874299858259, 4.89585800089753 52.59914699919396, 4.897500998701213 52.60063399964576, 4.898922999336264 52.60168799956481, 4.901330999758315 52.60299999922196, 4.904420000478519 52.60416499957144, 4.907561999391847 52.6055939987635, 4.910202001096703 52.60613599860861, 4.912790998661555 52.60753899884763, 4.914813999543097 52.60843799781961, 4.915553000161951 52.60866799890348, 4.916204998883521 52.60871299926947, 4.918089999392699 52.60862599950602, 4.921165999113145 52.60835099947237, 4.925337000248835 52.60751699929813, 4.926408999241633 52.60712300002748, 4.928069000313625 52.60571199956593, 4.928948001022889 52.60516499905069, 4.930969999212945 52.60563199875786, 4.934613001140649 52.60621100016279, 4.937695000015231 52.60737899864777, 4.939806999679639 52.60909899831762, 4.940588999901392 52.6094789996773, 4.942174000216836 52.61005399884632, 4.943906999494871 52.61085599954677, 4.948287999349943 52.61345899915831, 4.950534999809646 52.61409599898855, 4.951512000865442 52.61404299840017, 4.95227099866475 52.61383099887909, 4.95383299939021 52.6131559990545, 4.955293000333047 52.61228499873734, 4.956635000916435 52.61066799786041, 4.957597001254429 52.60888799864716, 4.958589000195564 52.60672400008669, 4.959320998968797 52.60471000038521, 4.958675999260386 52.60326299821409, 4.956941000264306 52.6022229990386, 4.954528000547141 52.60169799948881, 4.95252299996047 52.60117099882118, 4.950828000990221 52.60044899892765, 4.949258001392579 52.5995329996589, 4.94894200062202 52.59909799942479, 4.948832998993777 52.59859599927505, 4.949689000113056 52.59671999880537, 4.949907000537082 52.59554599838132, 4.949850000075403 52.59528499909043, 4.996217000702252 52.59744699795322, 5.001674998845236 52.59756299905423, 5.001910999564132 52.59769899858703, 5.002311999681467 52.59770099970479, 5.002428000322869 52.59756999942591, 5.004881000065865 52.59757299897823, 5.006127000020767 52.60157199988009, 5.006035998687393 52.60180099969722, 5.006219001213163 52.60197399937352, 5.006590999753916 52.60308199831539, 5.007949000914129 52.60779899970347, 5.006573999318068 52.6105059993753, 5.010179000937986 52.61095699863726, 5.010444000401002 52.61086200006764, 5.012322998923581 52.61107000018571, 5.012576000078325 52.61125100008419, 5.0154319996567 52.6115659998136, 5.016476999623423 52.61183999858013, 5.017465998987491 52.61182799895461, 5.01862799945464 52.61199099870667, 5.018800000698699 52.61205199951748, 5.018852998891825 52.61243799998164, 5.019659001395046 52.61245399901026, 5.019723000869883 52.61122699941693, 5.019253999009159 52.61056199951603, 5.019132999072642 52.61019500046516, 5.019174998816519 52.61000800004578, 5.019814999229817 52.609757999114, 5.020037998948958 52.60935099895096, 5.019975999192165 52.60902699914824, 5.01972900002402 52.60864599935398, 5.019342000765464 52.60703799854933, 5.019495998882191 52.60641900028102, 5.019681001126006 52.60615399875503, 5.020642998631538 52.60555199936604, 5.020797999439749 52.60497499907883, 5.022068998702681 52.60464199920268, 5.022359000306187 52.60435499954332, 5.022039000099538 52.60396999892945, 5.020976999696966 52.60330599887858, 5.021168001094918 52.60230000029438, 5.021095000056414 52.60217699882179, 5.020208000474971 52.60194299975078, 5.019668999985273 52.60189699811775, 5.019443000689066 52.60177999999846, 5.019548999907779 52.60177399947758, 5.019623000805305 52.60161800009691, 5.020137999013535 52.60168099919316, 5.020634999759356 52.60153899913958, 5.021002998864018 52.60156799906076, 5.02122600141562 52.60172399844144, 5.021168001094918 52.60189699811775, 5.021248001146581 52.60192899900748, 5.021357999801385 52.60166699844985, 5.022476000806613 52.60178199970002, 5.022875001205901 52.60168399874549, 5.023332998952452 52.60131499857633, 5.02333900093905 52.60115299867476, 5.023016001155333 52.60073899955574, 5.023204000143755 52.60009000009862, 5.023285999913462 52.60004999898645, 5.023954999070881 52.60014599882322, 5.024031999545475 52.60008499942851, 5.023972999365751 52.6000089980235, 5.023098000925041 52.59975799865678, 5.022804999744466 52.59956399928165, 5.023118000937956 52.59949799921682, 5.023372998978283 52.59965499844833, 5.024326000585072 52.59990399952972, 5.024484000970352 52.59975999977455, 5.024096998879335 52.5995279989888, 5.023656998595192 52.59911499972044, 5.023435998594098 52.59875399835735, 5.022745999564742 52.5982219984356, 5.022468998960992 52.59771199947956, 5.021599999674418 52.59719099933251, 5.020863998632632 52.59619199970278, 5.020484001219698 52.59595000040913, 5.020372000014386 52.59556499979475, 5.020575999579632 52.5953309993073, 5.02096399869721 52.5953109994593, 5.021252000582671 52.59515599851304, 5.021339999506515 52.59481299871237, 5.021188001107834 52.59419099947431, 5.023641000850829 52.59159899821348, 5.023568999671348 52.59145199890578, 5.023182000412794 52.59134599914486, 5.023977998660865 52.59025800004896, 5.025162998717997 52.58964999872137, 5.026745999315395 52.58765399916096, 5.027171998740759 52.58643699807065, 5.02858300092656 52.58521299944086, 5.028605000657521 52.58488299911601, 5.028465000567112 52.58450899827577, 5.027682000486337 52.5833609982184, 5.028826000658615 52.58262999966583, 5.029531000405774 52.58179499822122, 5.029671000496183 52.58150899982783, 5.02966600120107 52.58114299920984, 5.029585001290385 52.58100899937826, 5.029089000403586 52.58071299822838, 5.029119998865751 52.58062599988076, 5.030082999062767 52.57918099882196, 5.030797000373592 52.57725399887567, 5.031427999223224 52.57625299812504, 5.031640000493114 52.57525599960982, 5.032573999113549 52.57367399916572, 5.032468999753856 52.57326299959725, 5.031308999004754 52.57162099819224, 5.031346999312539 52.57115399848256, 5.031215000926773 52.57051300066112, 5.03100199979786 52.57037199904141, 5.030751001193624 52.56900599893802, 5.03077100120654 52.56829500023188, 5.030996000643725 52.56789499902168, 5.031652998660409 52.56708299980801, 5.032124000239179 52.56678999962604, 5.033141001320805 52.56579299969312, 5.033197998950022 52.56582799871916, 5.034196999736778 52.56535699960592, 5.034659999610904 52.5649279998904, 5.037326000482812 52.56382799974817, 5.039640999853444 52.56250099948878, 5.042314999597533 52.561261998844, 5.0426700005349 52.56095999858851, 5.042870000664056 52.56040199971081, 5.042946001279628 52.55842199916999, 5.04348699865491 52.55761699891061, 5.044944999879701 52.55634599879114, 5.045720001088295 52.55523999954273, 5.047641999213774 52.5533570001048, 5.048977000784005 52.55189699844562, 5.050845000857334 52.54957899876922, 5.053305999472512 52.54595699964587, 5.053842000385141 52.54539099912814, 5.054751999556569 52.54467699945079, 5.053950999180923 52.54151299854703, 5.05402899951454 52.54114799919375, 5.05392100057778 52.5404449992908, 5.05480600044118 52.53840199964694, 5.055309000341137 52.53800599925421, 5.055788000792088 52.5378580000945, 5.056160999191864 52.53763699908136, 5.057314000927809 52.5373579996415, 5.057660000301509 52.53718899936658, 5.060501999021104 52.53256799918029, 5.061536000538579 52.53160699811905, 5.06268500000597 52.52991400006447, 5.063961001396477 52.52942599923652, 5.064709000746535 52.52904599787207, 5.065466998686819 52.52792899884403, 5.065337999878122 52.52756799889313, 5.064920999183963 52.52725499886115, 5.065189001056509 52.52719700043432, 5.065854000777836 52.52645599911877, 5.065561999456284 52.52575399906498, 5.065535000430209 52.52496700036433, 5.066402999857761 52.52288100005047, 5.067312999029189 52.52212599940715, 5.06951200059088 52.52126399915089, 5.07009899997859 52.52076499854738, 5.070447998929359 52.51966499981375, 5.070856000892315 52.51932099874202, 5.071570999229699 52.51892899916749, 5.072294999130751 52.51880999992889, 5.073132000096138 52.51922399905324, 5.073670000726812 52.51935799888611, 5.073876999869127 52.51936999992803, 5.074891001373686 52.51914699921268, 5.076251999278506 52.51923799838051, 5.076226000111454 52.51916199980696, 5.075600000556935 52.51916499935933, 5.075005999323604 52.51906199914959, 5.072774999440725 52.51761599964226, 5.073012000018644 52.51745699929094, 5.074255000396478 52.51823000008147, 5.075313001362958 52.51869899949661, 5.075771998968532 52.51904299915214, 5.075851999020195 52.51902299930391, 5.075001999887514 52.51830499880424, 5.074350001165943 52.51803099861748, 5.074341999461299 52.51767199836759, 5.074582999475309 52.51722899932158, 5.074462999397815 52.51687499974182, 5.074205998806981 52.5166249988064, 5.074281999422553 52.51633299988913, 5.074203999088935 52.51599900015756, 5.074860000079059 52.51508599901284, 5.078883999278719 52.51381799985348, 5.080116001207303 52.51334599805318, 5.080637001402131 52.51304099824304, 5.081665000933007 52.51119599878759, 5.082685998618262 52.50876500037858, 5.083425999096138 52.50759199978913, 5.083589998635554 52.5068839992123, 5.08571199972265 52.50413299898054, 5.085692999568757 52.50375799828478, 5.085356998785283 52.50328599931617, 5.080926000314153 52.49968100046245, 5.077946001222194 52.49553199921819, 5.078339999493908 52.49473899857661, 5.078332000621726 52.49443999928657, 5.077705001208184 52.49357199850557, 5.076693999280694 52.49280799920163, 5.076968000307376 52.49256799818943, 5.076671999549733 52.49267899862217, 5.075647999454947 52.49226999875007, 5.073774000227481 52.49185799932556, 5.073603998701468 52.49211099981434, 5.072512999554777 52.49208000019107, 5.071885000282213 52.4919480000592, 5.071729999474002 52.491751999563, 5.071784000358612 52.49136699894233, 5.071624000255287 52.4909699986959, 5.069945998888425 52.48967599916244, 5.069549001039643 52.48957799962241, 5.068829000574683 52.48954799984993, 5.067256001399972 52.48968199968342, 5.067008999399365 52.4889189988136, 5.06710799960492 52.4888659996404, 5.064801998965493 52.48674099946886, 5.064499999053713 52.48673999961807, 5.062389999107351 52.48753099914284, 5.062123999785312 52.48750099937035, 5.061156000293183 52.48782199962686, 5.060889001112121 52.48767499890054, 5.060602998944706 52.4872169992581, 5.060314000032683 52.48705299823583, 5.059930000351196 52.48663199873761, 5.059932000069242 52.48638099795021, 5.059683001183051 52.48644699943241, 5.059274999220095 52.48477799920116, 5.059589000272609 52.48433999940683, 5.059611999862593 52.48406999862179, 5.059130999693596 52.48362999912582, 5.058962000859067 52.48315500060334, 5.059128999975551 52.48207999813311, 5.059062000923645 52.48179699928765, 5.059336998976888 52.48010799920679, 5.059988000671898 52.47893499861208, 5.060346001186333 52.47724599853049, 5.060571000623518 52.47670699964303, 5.061584999295614 52.47594400018788, 5.0637969990246 52.47482799958512, 5.06431499964236 52.47477999966578, 5.06478600122113 52.4745560005143, 5.06540900119858 52.47396199850304, 5.066034000894076 52.47287299953632, 5.066288998934404 52.47268900008153, 5.066738000782212 52.47267299963634, 5.067651999389732 52.47230399945971, 5.067723000710189 52.47213800015168, 5.067345000182838 52.47175699893302, 5.067485000273248 52.47152999881282, 5.067784000607959 52.4713279992112, 5.068267000495 52.471328999062, 5.068426000739302 52.47125799974186, 5.069089000742585 52.47079799898042, 5.069049000716753 52.47064199959654, 5.06876700081789 52.4704380002933, 5.068250000059153 52.47029399911899, 5.067983000878092 52.47004399959795, 5.067963000865176 52.46991700013584, 5.068550000252887 52.46896099973535, 5.068475999355361 52.46876699894003, 5.068117998840926 52.46860599888589, 5.067400000926472 52.46850699949474, 5.065576000315064 52.46807799835648, 5.063071999264526 52.46775899921679, 5.061904999502263 52.4677049987764, 5.057839000558727 52.46792599979195, 5.053184999535995 52.46871399976666, 5.051627001079087 52.46925399992181, 5.051067000717451 52.46933299804834, 5.049791999185968 52.46891999877156, 5.04970999941626 52.46878899849001, 5.048832001398482 52.46845699845755, 5.04842499929455 52.46843099808816, 5.042458999124033 52.46610800054366, 5.041572999401612 52.46589499833443, 5.041424000579998 52.46549799808641, 5.040401000344236 52.46565899955684, 5.040332998600846 52.46553699934862, 5.039980000213986 52.46559900001164, 5.040021000098839 52.46570899917783, 5.039805999251882 52.46574199991904, 5.039683999456344 52.46549199898161, 5.039304999069969 52.46532699810799, 5.039094000491565 52.46499699919316, 5.038081998705051 52.46498999882136, 5.036344000131903 52.4652209997612, 5.037245000572128 52.46658499876901, 5.035268998729576 52.46708699892915, 5.034581999277288 52.46696699983877, 5.034236999762609 52.46700799938636, 5.033974000017638 52.46709999840574, 5.033861998812327 52.46726399801229, 5.033605001053954 52.46738999903988, 5.033219998680982 52.46717799951393, 5.033210000090756 52.4667679997894, 5.033511000143513 52.46617899844762, 5.033094999308376 52.46552799927522, 5.033064000846211 52.46512299880457, 5.033170000064924 52.46488299920755, 5.033861998812327 52.46463999864189, 5.034290000788197 52.46417499862597, 5.034977000240485 52.46424499951161, 5.035950998886751 52.46416699840337, 5.036586000004936 52.4642130000374, 5.037730000177215 52.46408399945736, 5.038284001384715 52.4639039994055, 5.038652000489376 52.46368599935843, 5.038751000694932 52.46342499864592, 5.040682000384077 52.46211099784225, 5.038298999270054 52.4611449989326, 5.038192000192318 52.46100599842828, 5.036654998774887 52.46266699985939, 5.035161999651839 52.46357699862677, 5.034597999854112 52.46358299914778, 5.034415000160804 52.4636579978712, 5.034407001288622 52.4635799995954, 5.034904999061005 52.46324499859404, 5.035332001177853 52.46320500031344, 5.035704999577628 52.46288899930969, 5.037525000752945 52.4609999993235, 5.037563001060731 52.46087999881679, 5.037280001302845 52.46035699895695, 5.038735000118107 52.4597420000776, 5.038985998722343 52.45929299909033, 5.039306998788016 52.45786399986548, 5.039675000725139 52.45828099854617, 5.039486998904255 52.45861199872837, 5.039599000109567 52.45869299938908, 5.039349001364354 52.45972699948317, 5.039437000288197 52.45974900044941, 5.039400999698457 52.45981799865185, 5.039292000902675 52.45978799887923, 5.038753000412977 52.46052399953231, 5.041769000094677 52.46139099905078, 5.042071999865478 52.46133299920714, 5.04260500120104 52.46049299849266, 5.043053000357364 52.46043799961763, 5.043069000934189 52.46034799888354, 5.04331400038429 52.46044899939265, 5.045198001034445 52.46030299993272, 5.046073999334178 52.46118099922624, 5.046534999490259 52.46135999942737, 5.047615000187699 52.46198499964699, 5.047877000073647 52.46203699896962, 5.04808699879303 52.46223699886993, 5.048540999935952 52.46236299989762, 5.048911998617682 52.46262499904475, 5.049392998786679 52.46277999857802, 5.053455001126586 52.46226799849335, 5.054265000233436 52.46196999905258, 5.054952999544748 52.46136899808455, 5.055066000609083 52.46106099871953, 5.05482800017214 52.46056299796211, 5.053586999512352 52.45925299939349, 5.053410998832202 52.45930499871614, 5.053152001355785 52.45903799889865, 5.053257000715476 52.45896399860978, 5.052621999597291 52.45825099877354, 5.051918999568178 52.45780499875479, 5.052005998632999 52.45775899995315, 5.051777999618746 52.45747399998878, 5.050920998640445 52.45676799910798, 5.05110600088426 52.45638899900622, 5.051042001409423 52.45626999976645, 5.050689000190101 52.45595599988016, 5.050451999612182 52.45597899928099, 5.050208000021104 52.45589599891866, 5.049291998863078 52.45527599936865, 5.048717000616102 52.45523899922419, 5.048403999422611 52.45506899909629, 5.048499000192075 52.45424199927383, 5.048136000382527 52.45383199813225, 5.048298000203897 52.4534989982482, 5.048012000868943 52.45292399906507, 5.04850799892328 52.45222499855559, 5.048625999282728 52.45124899830401, 5.047998000010164 52.45026600051289, 5.048084999074985 52.44969699901804, 5.047548001135794 52.44861999825679, 5.048163999267624 52.44771699985908, 5.048357000383621 52.44760399972397, 5.048886999309653 52.44748800003643, 5.04912699946464 52.44732499886394, 5.049439000799108 52.44670499931311, 5.049424999940328 52.44622899952086, 5.049566999748783 52.44575299831234, 5.050308999944704 52.44495999908079, 5.04884999886089 52.4433559990543, 5.048895001014296 52.44317099833163, 5.048668998885628 52.44318800004392, 5.047952000830197 52.4422539991895, 5.04795700012531 52.44206699876518, 5.04811899994668 52.44191099796439, 5.048025998895261 52.44149800010205, 5.048276000472937 52.44137099922315, 5.048498000333053 52.44135799974651, 5.048678000449293 52.44115400044232, 5.048541999794975 52.44074099833127, 5.048545999231066 52.44023999801986, 5.048779000372894 52.4400930001249, 5.048684999462452 52.43980599904192, 5.04886799915576 52.43961699891593, 5.049416001209124 52.43935899917107, 5.050131999405533 52.43915699956843, 5.050972999807009 52.43926199948106, 5.052330001108201 52.43903199839109, 5.052836000585226 52.43911699987147, 5.053068998894592 52.43900799913938, 5.05394200044972 52.43897799936667, 5.054337001412917 52.43910600009642, 5.055011999724472 52.43913599845292, 5.055593999817068 52.43880399841891, 5.056421999218789 52.43885199833852, 5.057713001327097 52.43920899889124, 5.058135001316369 52.43871499895154, 5.058627999793639 52.43859999911465, 5.058545000164909 52.43825399975317, 5.058888999820564 52.43806999888115, 5.060133999916443 52.43790299972143, 5.061175000447076 52.43762699841339, 5.063429999778961 52.43752599932016, 5.063671999651993 52.43733799904491, 5.064178998988041 52.43730199875115, 5.064842998850346 52.43734299971515, 5.065171000761639 52.43752399961854, 5.06526199926255 52.43770000026795, 5.065127001299716 52.43789499949879, 5.065285998711557 52.43800099784603, 5.065354000454947 52.43835699996422, 5.065827998778323 52.43867399940377, 5.066628999153968 52.43885299818932, 5.06770899985141 52.43890400049377, 5.068315999252036 52.43883099863923, 5.068732000087172 52.43861599955993, 5.068969000665091 52.43836600003766, 5.069323998769996 52.43823399990469, 5.070866999341564 52.43824399982893, 5.072435999080183 52.43726299890498, 5.072896999236264 52.437268999426, 5.073241998750943 52.43744199910677, 5.073536999649562 52.43749099887719, 5.074406998795159 52.43742099940749, 5.075167999144973 52.43727299882921, 5.075965999943552 52.43728299875346, 5.076813999358187 52.43707799818212, 5.077425001027366 52.43669299897437, 5.0778260011447 52.43657899898825, 5.080537001337553 52.43652899936703, 5.086632000316767 52.43708799810636, 5.087032000575078 52.43695199857016, 5.087098999626984 52.43654599966311, 5.08729899975614 52.43637899908713, 5.08674800095817 52.43566299969598, 5.086693000214536 52.43542199883081, 5.087319999628078 52.43500999940263, 5.088360000299688 52.43482899949915, 5.088845999763798 52.43484299882661, 5.088942000392285 52.43491900023361, 5.091301999083861 52.43738099971052, 5.092637000654092 52.43930799828284, 5.093387999581218 52.44126999871375, 5.094398998676246 52.44859899855754, 5.094363000918968 52.44867299884655, 5.093934998943097 52.44871099884184, 5.093733998954919 52.448845998527, 5.092951998733166 52.45045199966994, 5.092608998936533 52.45147200043795, 5.092637000654092 52.45166200041437, 5.093355001401007 52.45206999902206, 5.094569000202259 52.45313299903909, 5.094684000984639 52.45414399973325, 5.098756998941335 52.45723199927364, 5.099252999828133 52.45823499974454, 5.099072999711893 52.45841399852954, 5.099269000404958 52.45877399863356, 5.09994299885749 52.45868199961406, 5.100063998794006 52.45875699975375, 5.101536000877577 52.46205499911643, 5.100537000090821 52.46404900043076, 5.100550001090578 52.46428899861161, 5.102709999652999 52.46665299995301, 5.102744000524694 52.46678899948859, 5.094067000161324 52.48846699969221, 5.094209999828802 52.4886399993716, 5.094418998689162 52.48863400026683, 5.094533999471542 52.4885519983395, 5.095971000824395 52.48533899905406, 5.102590999434527 52.46879499901102, 5.102718001357642 52.46860199806649, 5.102880001179011 52.46857499926251, 5.10309199961644 52.46829999922281, 5.10393399987694 52.46770399892561, 5.104926998677097 52.46727599905431, 5.106158000746659 52.46753699976666, 5.106515001402071 52.46752899954406, 5.106686999813668 52.46745499783905, 5.108763998747358 52.46544099950982, 5.11200700041675 52.46381199896987, 5.112569000496432 52.46304799966237, 5.112530000329624 52.46242399929364, 5.11364499892532 52.46178899914986, 5.118905999348678 52.4617159987118, 5.120098001251431 52.46179299996927, 5.132080000208522 52.46309799928338, 5.134877999466196 52.46285500013389, 5.135141999070189 52.46296099848071, 5.135457999840748 52.46273399836019, 5.13652399967941 52.46240499929603, 5.137219000836341 52.46193499860975, 5.137572999082224 52.46125499809887, 5.137489999453493 52.46084899919337, 5.137325999914077 52.46066099891881, 5.137595998672207 52.46038699872958, 5.138682001356246 52.4599510000514, 5.139230000577149 52.45995699915619, 5.139366001231467 52.45980999842924, 5.139243998603466 52.4596679983725, 5.138998999153365 52.45964699867329, 5.139012000153123 52.45953199883677, 5.139294000051986 52.45950399876574, 5.139275999757116 52.45943699884867, 5.138639998779908 52.45947899966333, 5.137954999045665 52.4592599997653, 5.137901000993516 52.45931999931056, 5.138671999933558 52.4595520001014, 5.138443001060282 52.45982299932207, 5.1376149988261 52.46020999823001, 5.136548998987438 52.4603009988149, 5.133516998728914 52.45982999969388, 5.131139999601491 52.45963299934588, 5.130211000276169 52.45899799920181, 5.129148000014576 52.45884800033866, 5.126930001131454 52.4581930003461, 5.12560099871536 52.45769399973766, 5.123208998870133 52.45648899966458, 5.121924998607446 52.45564599939706, 5.121625001246175 52.45553999963389, 5.11758900090578 52.45504699813004, 5.116715999350653 52.45513499916261, 5.114512001326311 52.45488299852317, 5.111085999963612 52.45404599877646, 5.110121999907573 52.4533820001262, 5.109057999786956 52.45220599997417, 5.108258999129356 52.45162999952389, 5.107862001280575 52.45115299988121, 5.107116998675123 52.45084199954707, 5.106177000900552 52.45019399850928, 5.103658998991235 52.45016599843822, 5.102907000205086 52.45026199969347, 5.100120999255685 52.4503559998309, 5.099123001160413 52.45026800021449, 5.096960000188463 52.44924400004309, 5.096237000146434 52.44874100003075, 5.095016999358584 52.44864599862626, 5.093962000801634 52.44135999944811, 5.093261000490566 52.43942199968513, 5.092239999972849 52.43775499914317, 5.089201000701165 52.4342929987443, 5.086755999830351 52.43209199856378, 5.086477999367578 52.43204699961277, 5.086356999431063 52.43210399960586, 5.086348000699858 52.43229599928448, 5.088212001337098 52.43423199934801, 5.086745001381102 52.43477399920768, 5.086573999996066 52.43475099980677, 5.085923001133517 52.4350459982802, 5.08330799873669 52.43315399873334, 5.082977000080792 52.43254299925458, 5.083092000863171 52.43245299852009, 5.083021999401736 52.43226999891493, 5.082533000360558 52.43169300002828, 5.081695999395172 52.4310569986144, 5.080846000262491 52.43069499880722, 5.079804999731858 52.4307469981301, 5.079608999038793 52.43070099932824, 5.079557000704689 52.43050199927774, 5.079346999152845 52.43035399869932, 5.078249000992995 52.42993599874976, 5.076369999637954 52.42886099910301, 5.075900000750669 52.4280949986735, 5.076086000021045 52.42795699801926, 5.076833999371103 52.42773999923798, 5.07683299951208 52.42697399880834, 5.076727000293366 52.42697899806236, 5.076621001074653 52.42667900033455, 5.076045999995214 52.42676899965289, 5.076008999546451 52.42660699833068, 5.075771998968532 52.42650000013244, 5.075360000401949 52.4266089994485, 5.07506199992626 52.42654199953109, 5.074999000310445 52.42614599913143, 5.074055000267322 52.42466199960759, 5.074161999345058 52.42455599842769, 5.075341000248056 52.42425499943274, 5.075219000452517 52.42422499824372, 5.074102999165335 52.42445099993103, 5.073021998608871 52.42406100005228, 5.073199999007066 52.42341999938362, 5.073081998647618 52.42287699967218, 5.073317999366514 52.42193999926236, 5.07337399996917 52.42098100071849, 5.073264001314366 52.42058199934985, 5.07294200138967 52.42019399917248, 5.072277998694904 52.4196579984164, 5.071607999678463 52.41923299951051, 5.07106699947072 52.41899899901657, 5.070324999274797 52.41832299932057, 5.068895999626587 52.41754600053093, 5.068456999201468 52.4171379990886, 5.068262001199887 52.41654399990444, 5.068412999739546 52.41545299981009, 5.067864000659621 52.41463799960837, 5.067494998863475 52.41353599832239, 5.067157001194417 52.41342000005045, 5.062510999043867 52.41422799846425, 5.059285000642785 52.41465299878668, 5.057486999198428 52.41481399884217, 5.056590001026757 52.41469799915405, 5.055452000008614 52.41437399934144, 5.051835000080425 52.41300899905544, 5.051109000461327 52.41250800015808, 5.048330001357547 52.40934499903955, 5.048099999792787 52.40885799946942, 5.047590000879671 52.40841999966988, 5.04625499930944 52.40673299927123, 5.044359000351013 52.40634099968978, 5.043647998617256 52.40611499800185, 5.042770000599479 52.40602199913079, 5.041961001351651 52.40555399814195, 5.04123399904107 52.40527300041092, 5.039082999209853 52.40503499909703, 5.038372000308558 52.40487199933967, 5.036111998849098 52.40329699922518, 5.035628998962056 52.40307599962362, 5.033961998876904 52.40254499953635, 5.032682000882769 52.40119899924562, 5.031782000301568 52.40103599948817, 5.03139099877446 52.40081199891787, 5.031200000208971 52.40044799940735, 5.029555999713804 52.39888299921608, 5.029060998686028 52.39800899931722, 5.028447000272243 52.39640899868216, 5.028531999619018 52.39576799801073, 5.027904000346453 52.39522499971315, 5.027645999896596 52.39461899948479, 5.027416001164298 52.39356499953152, 5.026593001057691 52.39267099836709, 5.026284999159314 52.3913779986645, 5.025874000451753 52.39074199866283, 5.025583998848246 52.38986399935962, 5.025336999680101 52.38949699887982, 5.025177999435799 52.38871799896884, 5.025238999333569 52.38817599910525, 5.02476800058726 52.38794099875937, 5.023529999504539 52.38765199938863, 5.02333900093905 52.38754799932595, 5.022490998691953 52.38737399979303, 5.021635000405135 52.38739399964166, 5.019979998628256 52.38695699969146, 5.019262000713802 52.38668800016934, 5.018887999622542 52.38644099878124, 5.018041999925952 52.3849749993952, 5.017644999244709 52.38462999988161, 5.017137000049638 52.38437399841992, 5.016163001403372 52.38424099985124, 5.014117000931847 52.38456099884592, 5.008455000391155 52.38272800039547, 5.009098000381522 52.38280199926903, 5.012712000732643 52.38400199868576, 5.013007998657825 52.38404799890415, 5.013136000439961 52.38396199898845, 5.01308199955535 52.38348299822287, 5.012061998896656 52.38336299913092, 5.011830000446313 52.38340599979689, 5.011290000097592 52.38329499936224, 5.010851999531495 52.38306100028321, 5.010914999147309 52.38264499861597, 5.011206000609839 52.38217099993678, 5.011459998791143 52.38214099874757, 5.01204199888374 52.38230199880388, 5.0124589995779 52.38225199918223, 5.012757000053588 52.38235499939419, 5.012973000759569 52.38226799821143, 5.013303999415467 52.38199099846639, 5.013010001208332 52.38175799923813, 5.013400000043955 52.3818389998998, 5.013561999865324 52.38179199841436, 5.01338799890322 52.38132899951025, 5.013782999866418 52.38081900053687, 5.013567999019461 52.37874499980184, 5.013238000222584 52.37837899917221, 5.012797999938442 52.37816699822708, 5.012634000399027 52.37778099916496, 5.012393000385017 52.37752599897004, 5.012791000925283 52.37734800003357, 5.013064999119504 52.37712399946246, 5.013038999952451 52.37702200051744, 5.012673000565835 52.37661699862495, 5.012403998834266 52.37651599953076, 5.012251000576562 52.37611699815933, 5.011739998971962 52.37589899810924, 5.011686000919813 52.37576499827336, 5.012095999768351 52.37491899844274, 5.011669000483965 52.37462999907142, 5.011947000946738 52.37445100028407, 5.011856999472387 52.37425300008262, 5.011514999534777 52.37404399868979, 5.012859999695234 52.37321899997466, 5.012809001220154 52.37318499796594, 5.011886001048969 52.3737059981313, 5.011508000521617 52.37352899904555, 5.009937001064953 52.37310899939065, 5.009546999396869 52.3729259997838, 5.009195000869031 52.37298099865958, 5.008359999621691 52.3725639999733, 5.00775499993911 52.37270699988279, 5.006537998728328 52.37353899896989, 5.001760001024474 52.37491799859193, 5.001306999740574 52.37527099974497, 5.000814001263306 52.37708499961592, 4.999903999259416 52.37823399956122, 4.998529000495816 52.37878299979755, 4.998114999378725 52.37865899846985, 4.997735998992352 52.37832399888025, 4.997494998978342 52.37835299880245, 4.997043000385927 52.37822199993526, 4.99595700053435 52.37815199904868, 4.994751000605279 52.37771800006652, 4.994272000154329 52.37776199916711, 4.993930000216718 52.37789299945052, 4.993124000545959 52.37754999963818, 4.992585000056261 52.37716199804196, 4.99131400079333 52.37722199900421, 4.99074900113658 52.37742599972666, 4.990568001161318 52.37738600002931, 4.989829000542464 52.37758599993227, 4.989490000181922 52.37756799978523, 4.987859000686511 52.37682699986851, 4.987447999146488 52.37635000022023, 4.985316999328187 52.37508199820086, 4.984531999529366 52.37476299905652, 4.98376300030737 52.37403199906382, 4.98306499957337 52.37389599952626, 4.982347998685476 52.37390100019655, 4.982003999029821 52.37376999991304, 4.981800999323598 52.37350699949523, 4.981656999797098 52.37346099927677, 4.981071000268409 52.3737739993163, 4.980378998688546 52.37366799955177, 4.980120001212128 52.37371499820483, 4.978501000024989 52.37576599812417, 4.976979999184382 52.37688399986211, 4.973965999220728 52.37944200061774, 4.97410699917016 52.37893799933286, 4.974098000438955 52.37806999995237, 4.974319000440049 52.37648999916098, 4.974238000529365 52.37638599909813, 4.974046999131414 52.37636399954782, 4.973881999732975 52.37646199908959, 4.973639000000921 52.37808599898158, 4.9734879986288 52.37825999993099, 4.972992000574463 52.37981299908515, 4.969684999430234 52.37938299809011, 4.9693849992365 52.37926699981755, 4.968595000142566 52.37932199869329, 4.965771998744401 52.38186500026956, 4.965208998805696 52.38215499807514, 4.964214000287493 52.38297899977074, 4.963295999411422 52.38358599985101, 4.961162999875076 52.38180099848788, 4.961503999953663 52.38165299932454, 4.961486999517816 52.38142499935033, 4.95987200059923 52.38214899897028, 4.960695000705837 52.38172099909323, 4.960619999949287 52.38165899984563, 4.960486998872037 52.38159899888342, 4.959558999405739 52.38198299824369, 4.961219000477731 52.38119999892853, 4.960946999169096 52.38115899938039, 4.960296000306547 52.38144099837952, 4.960197999960014 52.38131099936321, 4.96037600035821 52.38123399952099, 4.959961999241118 52.38096699970024, 4.959547001097467 52.38093399895859, 4.958992999889967 52.38106899864518, 4.958857999094671 52.38096200044619, 4.959072000082607 52.38081599956822, 4.95875699917107 52.38060799802643, 4.959050000351645 52.38058699832695, 4.961618000877021 52.37990699922361, 4.961867999622235 52.37978299931215, 4.961743000249628 52.37960899977899, 4.959997999830859 52.38002699831561, 4.959908001188969 52.37984499855976, 4.961632998762362 52.37938799876038, 4.961388999171284 52.37905699857407, 4.961148999016297 52.37912699946065, 4.96125400120845 52.3792849999646, 4.959964998818187 52.37964399880605, 4.959860999317518 52.3794699992729, 4.961632998762362 52.37850399893453, 4.961751998980832 52.37858699929787, 4.961974998699971 52.3784589985669, 4.961861000609076 52.37838299999168, 4.962051999174566 52.37827599896019, 4.97695000058124 52.37036699917473, 4.97899800077081 52.36944299965022, 4.98120400134566 52.36863999906861, 4.983492998716779 52.3679539994429, 4.985751000458193 52.36740099980238, 4.989551999938714 52.36677099890321, 4.991825999424492 52.36654599989711, 4.994077999179309 52.36644099856706, 4.996625999691768 52.36644499938654, 4.998567000803602 52.36653599997277, 5.013941000251698 52.36796799877051, 5.013986999431665 52.36804999928318, 5.014054001316032 52.36798799861921, 5.014513998780629 52.36801499883983, 5.014494998626736 52.36793499944502, 5.014079000624061 52.36789899915086, 5.014032998611633 52.36782199930848, 5.013980000418506 52.36788200027081, 4.998545001072641 52.36642799909026, 4.994634999963877 52.36631299925207, 4.990595000187392 52.36656199892635, 4.98692699895166 52.3670859986447, 4.983670999114973 52.3677919981192, 4.982155000401942 52.36821699986096, 4.979675998659433 52.36905799902217, 4.97899800077081 52.36878499867981, 4.978626999256619 52.3687170003272, 4.973974000925372 52.36867199854329, 4.973594000679975 52.36851799885854, 4.973252000742365 52.36784799826206, 4.973288001332106 52.36756100000826, 4.975828999998945 52.36607599920377, 4.975756998819464 52.36587599930042, 4.97602500069201 52.3657279987205, 4.975551999395195 52.36537799853558, 4.97525800118806 52.36553899859232, 4.974974998597711 52.36548000031321, 4.974345999466124 52.36581699960511, 4.973642999437011 52.36539999950215, 4.972628000905892 52.36508899774761, 4.972149000454941 52.36539899965133, 4.971392999400239 52.36547399979212, 4.970338000843289 52.36578199774553, 4.969589998660769 52.36566100021867, 4.969321999620686 52.36570499931935, 4.968764998836117 52.36650700005051, 4.968793000553676 52.36712199893886, 4.969157000222247 52.36758799881266, 4.969895000982078 52.36817699874732, 4.969124999068598 52.36862799944262, 4.965091001278711 52.36859400026632, 4.96505500068897 52.36789100034435, 4.962977998922819 52.36786599982536, 4.962991999781598 52.36857600011925, 4.958394999220545 52.36852299811261, 4.958122000885347 52.3684949994574, 4.957942000769107 52.36834199820724, 4.957872999166694 52.36811399964877, 4.957959001204955 52.36769499984432, 4.957777001370669 52.36766199910261, 4.957812999127947 52.36750999911946, 4.95800799996199 52.3675249997141, 4.958161001052156 52.36686600030891, 4.959434000033133 52.36711299886532, 4.959482998790167 52.3669439985859, 4.95965300031618 52.36686499904187, 4.959554000110625 52.36645699901252, 4.958608000349457 52.36626299963029, 4.95846500068198 52.36578899953367, 4.95893699928731 52.3657279987205, 4.960182999242212 52.36599699965973, 4.960841999809404 52.3660459994307, 4.961057000656362 52.36598500003375, 4.961387999312261 52.36564899917646, 4.961425999620046 52.36511599938446, 4.96208200061017 52.36508999901464, 4.962289999611507 52.36436399969112, 4.962022000571423 52.36408699994531, 4.96158200028728 52.36398699928549, 4.961501000376595 52.36373799961111, 4.961304999683531 52.36369499894501, 4.960463999282053 52.36393699966371, 4.96005600015156 52.36414099897036, 4.959394999866324 52.36508899774761, 4.959309000660525 52.36507399856918, 4.959828001137307 52.36417200001046, 4.960522999461777 52.36379799915727, 4.962194998842042 52.36331199943447, 4.965169998638888 52.3618699992951, 4.969172000940049 52.3607199980798, 4.97318199928093 52.36068999972293, 4.9739990002334 52.36043099870783, 4.974164999490862 52.36027000006719, 4.973996000656332 52.35997899957736, 4.974105999311137 52.35993199950797, 4.974217000657426 52.35882899977794, 4.974155000900634 52.35803900008803, 4.974062999708238 52.357956998159, 4.97419300120842 52.35776499847815, 4.974712998711763 52.35765599916091, 4.975271999214376 52.35715199787431, 4.976417999104701 52.35731999830319, 4.978203999408324 52.35740799933711, 4.979742000684777 52.35766799878692, 4.98109999901253 52.35755799961886, 4.982756000648432 52.35783599921577, 4.983286999433487 52.35807199941357, 4.984193999027847 52.3579379995773, 4.984289999656333 52.35808899970986, 4.984468999913551 52.35810299903749, 4.98535799921304 52.35780399974103, 4.985815999792053 52.35754699984367, 4.986055000088017 52.35765999856418, 4.986551000974816 52.3577079999006, 4.986524998975303 52.3580039982284, 4.987358000504598 52.35817099880644, 4.989029000025841 52.35729999845445, 4.989404000976123 52.35739300015866, 4.990006001081635 52.35707399959721, 4.990128000877174 52.3571619992149, 4.988123000290503 52.3582459989473, 4.98819700118803 52.35863999823334, 4.986450001051216 52.35955199813342, 4.986466998654601 52.35960500014014, 4.987049998606221 52.3598499989952, 4.987405999402611 52.35966099886685, 4.992078000720213 52.36165799976564, 4.996530998922304 52.35969099863994, 5.004479999948531 52.35545099955068, 5.005408999273851 52.35521799890524, 5.006264000534108 52.35515699950818, 5.006419001342319 52.35508499891972, 5.006746000562127 52.35485799879537, 5.007369000539578 52.35414899835078, 5.009007998907171 52.3532809989669, 5.00960499971757 52.35321299919791, 5.015980998736625 52.35531599986355, 5.016521998944368 52.35535299859237, 5.01687500016369 52.35510799973714, 5.01723899983226 52.35451299928017, 5.017246998704442 52.3543609992967, 5.017131000895501 52.35429299952772, 5.014584000242064 52.35352399812058, 5.014209999150804 52.35359899967772, 5.013767999148616 52.35384599823465, 5.013137000298984 52.35377199936077, 5.01194099896014 52.35339199940207, 5.011684001201767 52.35317899860529, 5.012301999051643 52.35260599911474, 5.012779999643572 52.35182599934843, 5.013103999286312 52.35148799878877, 5.013393001030796 52.35079699990715, 5.013794001148129 52.35035599913495, 5.013701000096711 52.35012399975636, 5.014104999791114 52.34941899871449, 5.015445000656457 52.34801199901543, 5.015400001335513 52.34795699872321, 5.015225000514386 52.3480589990849, 5.014956998641839 52.3480369995345, 5.016742998945462 52.34590299924248, 5.018746999673111 52.34770100009248, 5.019051999161958 52.34787799917895, 5.023890999596043 52.34804300005562, 5.025765998682532 52.34841699949351, 5.027000000329162 52.3487859996773, 5.027510999101301 52.34867999849615, 5.028732999607197 52.34771199986768, 5.031722000262821 52.34600699930583, 5.032863000858033 52.34541199884801, 5.035010001253157 52.34447299872519, 5.035034000702164 52.34419199815901, 5.034088000940995 52.34086399896221, 5.034142998852167 52.34068799831015, 5.026022999273366 52.33848099899242, 5.025885998760026 52.33839999974641, 5.025997000106315 52.33826099923942, 5.028047000013931 52.33880499880889, 5.028056998604157 52.33857299942986, 5.026287998736382 52.33809899933118, 5.025470000757351 52.3376019998312, 5.025535999950234 52.33749399894822, 5.026293000863958 52.33749600006607, 5.027509999242278 52.33787799972735, 5.027776001396779 52.33745099828194, 5.02770500007632 52.33718999898048, 5.027965000244222 52.33699300004491, 5.027955998680557 52.33680799931916, 5.028359001348399 52.33656599859945, 5.028757998915225 52.33648000009932, 5.029570000572583 52.33662399986044, 5.030052000600602 52.33656299904699, 5.030176999973209 52.33643899913468, 5.029927001227994 52.33610499939368, 5.029397999328523 52.33597299925859, 5.02881699909495 52.33570599943595, 5.028790999927898 52.3356250001899, 5.028929000300261 52.3355469990801, 5.028978998916319 52.33534099865468, 5.029258999097138 52.3351519985256, 5.029417999341439 52.33516899882196, 5.02981400016366 52.33481799878454, 5.02965200034229 52.33460900022286, 5.029781999010011 52.33443399942144, 5.030184998845391 52.33430199928632, 5.030471001012806 52.33441100002018, 5.030799999950659 52.33417399855447, 5.030825999117711 52.3338469991853, 5.031266999260877 52.33382599948567, 5.031662000224075 52.33363799920737, 5.032334998817584 52.33352099966694, 5.033545001015208 52.33355899966293, 5.035592001345755 52.33341899930502, 5.036386999734803 52.33347899885146, 5.037109999776831 52.33372700009243, 5.037438998714685 52.33410199938199, 5.037621001381432 52.33453299881468, 5.037616999112879 52.33502299935916, 5.037392999534718 52.3359539992606, 5.037474999304425 52.33675299902684, 5.036862000749663 52.33778299973744, 5.036493998812539 52.33793299860344, 5.03539600065269 52.33808399873647, 5.032748999934675 52.33811699947834, 5.032787000242461 52.33827699826873, 5.0331539994881 52.33832099878582, 5.035203999395716 52.33835699908013, 5.035360000062949 52.33824099939062, 5.036262000362196 52.33815499805808, 5.039080999491808 52.33762699893409, 5.039661999725382 52.33742299821038, 5.041289999643725 52.33660999911655, 5.041735999082004 52.33666499940887, 5.042808000907264 52.33633199951871, 5.043238999627741 52.33608499812866, 5.043055999934433 52.33576199957909, 5.043132000550004 52.33555899870613, 5.043805999002536 52.33535199842989, 5.045873999205021 52.33529899925542, 5.048438000294306 52.33586699807721, 5.049287999426987 52.3362239986357, 5.050756999101029 52.33654199934737, 5.052458999916898 52.33706399936655, 5.053368999088326 52.33719399979997, 5.055503001316157 52.33777899891795, 5.05595400004955 52.33777299981304, 5.057604999557876 52.33750599999046, 5.059627000580395 52.33777699921632, 5.060479999290144 52.33775299854802, 5.061700999937017 52.33760399953284, 5.06368500065175 52.33709999966086, 5.065101999159226 52.3370020001185, 5.066450998755774 52.33663100023239, 5.067136001322479 52.33668999992798, 5.068224000892101 52.33700599952177, 5.068399998739789 52.33745999835552, 5.068451999906355 52.34210499935144, 5.068563001252643 52.34210299964981, 5.068550000252887 52.33778899884235, 5.068428000457348 52.33719299994915, 5.068531999958017 52.33717499980199, 5.068124000827524 52.33643699801681, 5.068410000162478 52.33639399876675, 5.069225001396903 52.33420299847686, 5.069225001396903 52.33392799984759, 5.069596999937655 52.33339500005294, 5.069779999630963 52.33340899938062, 5.070018000067906 52.33280499884803, 5.069877999977496 52.33278199944675, 5.069971001028915 52.33248999910484, 5.06971000100199 52.33215199996038, 5.069833000656551 52.33213199869534, 5.06980399907997 52.3320589982558, 5.069614000373504 52.33208799959442, 5.069580999360831 52.33203099960043, 5.069812000784614 52.33198599923248, 5.069520999322084 52.33111199932446, 5.068857999318801 52.3301389986068, 5.069272000435893 52.33000499876995, 5.070452001197912 52.33152599846098, 5.070748998982116 52.3322539989061, 5.070524999403954 52.33231499971959, 5.070380999877454 52.33346799907624, 5.070503999532015 52.33366799898059, 5.070357000428448 52.33389199955324, 5.070387998890613 52.33405699901405, 5.069963999183295 52.33441000016936, 5.069769001181714 52.33494399981474, 5.069943999170379 52.3353819982031, 5.070480999942031 52.33553399960324, 5.070460999929116 52.33572599928476, 5.069892000836276 52.33584499852677, 5.069977000183052 52.33601199910539, 5.069804998938993 52.33614600035833, 5.06984699868287 52.33650099979894, 5.069715000297103 52.33677499857729, 5.069865998836763 52.33697400004694, 5.069786998644123 52.33710299921334, 5.069889001259208 52.33729999814889, 5.069705998733438 52.33748199790596, 5.069610000937413 52.33835000012441, 5.069589001065474 52.34024499783562, 5.069676000130295 52.34200899951077, 5.069791000912675 52.34200400025669, 5.069730001014905 52.33829899923538, 5.069805998798016 52.33753899931612, 5.06991500042626 52.33743199970018, 5.070968999124187 52.33733199903994, 5.072444000784826 52.33810199888363, 5.072927000671868 52.33823399901868, 5.073775000086505 52.33832899900857, 5.074628998655276 52.33874399941168, 5.075580000544019 52.33897999961019, 5.076869999960843 52.33965799901624, 5.077594999720917 52.33980099892647, 5.081210999790084 52.33961899916947, 5.088697000942185 52.3389359990931, 5.089695998896479 52.33876699881294, 5.093764000390523 52.3387390001576, 5.095890000913711 52.33758099871536, 5.099355999469779 52.33617799983318, 5.099693999971299 52.33576800010022, 5.10086799874672 52.33489999929764, 5.101715001134795 52.33464399925017, 5.102354998715631 52.3345820000021, 5.103544001041317 52.3332739982768, 5.106632998929058 52.33159299837939, 5.107250999611395 52.33144700033274, 5.107994999525363 52.33143899869376, 5.108069000422889 52.33159899890053, 5.108546001155794 52.33191999916489, 5.109072000645735 52.33202299937766, 5.109385998865787 52.33171899940967, 5.109362999275803 52.33152199905771, 5.109624999161751 52.33144299809703, 5.109887998906721 52.33120099879334, 5.110000999971057 52.33041599977031, 5.110178000510229 52.33023399859682, 5.111083000386543 52.32996399922148, 5.111525000388731 52.33002899943827, 5.113154000166097 52.32983199908626, 5.119043999721042 52.32959600030364, 5.119075001015669 52.33033099828848, 5.119825000083772 52.33046299983991, 5.120792999575901 52.330429999098, 5.120625000600395 52.33026299993546, 5.120790999857856 52.33009700062373, 5.120787000421766 52.32957199963531, 5.12157000050254 52.32953199852144, 5.122659999790208 52.32921999974678, 5.123184999421127 52.32925399892329, 5.123799000667374 52.32949399852543, 5.124221000656647 52.32898799895119, 5.125001001160355 52.32879899882192, 5.125545000945166 52.32812099941471, 5.12617699965382 52.3276129987225, 5.126286001282065 52.32708199862896, 5.12689300068269 52.32682300044494, 5.127581999853025 52.32678800000137, 5.12831800089481 52.32655099853537, 5.128835998680108 52.32596000031145, 5.12881499880817 52.32560499945403, 5.129215998925504 52.32539999887911, 5.129744000965952 52.32543900014219, 5.129975999416295 52.32557799923324, 5.130157999250581 52.32595200008867, 5.130513000187948 52.32623399909024, 5.131357000166494 52.32654399816341, 5.132205999440152 52.32697499901283, 5.133384000484126 52.3270099994564, 5.133575998908638 52.3271209984758, 5.133657998678346 52.32734099964541, 5.133434998959206 52.32764499819739, 5.134986001235417 52.32686099902475, 5.137153998670019 52.32530699859065, 5.13996499892745 52.32672999874028, 5.137835998827194 52.32841799901092, 5.136960000527461 52.32938099980443, 5.137232998862659 52.32944299905254, 5.137677001415354 52.32896999880401, 5.138008999930276 52.3289659979845, 5.138323000982789 52.32872999920184, 5.138347000431795 52.32852699974488, 5.13853599927924 52.32834300028591, 5.139216999577392 52.32803499949826, 5.139942999196489 52.32810599881998, 5.140295000556788 52.32801399979861, 5.141005999458082 52.32828799857731, 5.141176000984096 52.3282309999995, 5.141320000510595 52.32826599902683, 5.142867000518254 52.32912799930919, 5.143612000291244 52.32963999940456, 5.144921999720983 52.33140299839939, 5.144958000310723 52.33159799904971, 5.144797000348376 52.33188399887054, 5.144131000768025 52.33220199958242, 5.144101999191444 52.33240299933762, 5.143452000187919 52.33316999821324, 5.14193700133391 52.33360699958333, 5.141141000253378 52.33392799984759, 5.140689998687524 52.33398899924484, 5.140437000365242 52.33412599863407, 5.139891000862385 52.33456999895984, 5.139073999909915 52.33504499890961, 5.137765000339198 52.33646499950461, 5.13688799934798 52.33703999869825, 5.135061999018527 52.33783499906108, 5.134483001335459 52.33797399815186, 5.134157999001236 52.33788500009929, 5.134554999682479 52.33806999940879, 5.135191000659686 52.33858099965263, 5.136021999638475 52.33875299948527, 5.136117000407939 52.3389669987171, 5.135898999983914 52.33915899981475, 5.135512000725358 52.33925399980463, 5.134719999080916 52.33868399844906, 5.134191000013907 52.33843899959319, 5.132650999019408 52.33793299860344, 5.132873998738548 52.33773899922035, 5.132776001224476 52.33767600012149, 5.132685999750126 52.33773099899758, 5.132402999992239 52.33767299915282, 5.132146999260428 52.33754099901777, 5.132185999427236 52.33708999973648, 5.130964998780364 52.33764899848453, 5.130903998882594 52.33776699929191, 5.130611000534481 52.33769400026866, 5.129951000108267 52.33780899869115, 5.129952999826312 52.33814199858123, 5.129160001155309 52.33868899911937, 5.129501001233898 52.3398729995151, 5.131206998653395 52.34192199974369, 5.13094199919038 52.34377399962004, 5.131256000242892 52.34346499898231, 5.131616000475374 52.3435839996404, 5.132023999605867 52.34357799911928, 5.132146999260428 52.34369999932982, 5.131993001143702 52.34373599962411, 5.132572998685792 52.34392499975294, 5.132717001044753 52.34391200027609, 5.132235001016733 52.34376299984483, 5.132322999940577 52.34368599858594, 5.133023000392622 52.34371300022288, 5.133106999880376 52.34384399909078, 5.13294300034096 52.3438559987168, 5.133350999471453 52.34400499914805, 5.133207999803976 52.34387300042935, 5.133305000291486 52.34381699887005, 5.133861001217031 52.34387699983261, 5.133955999154034 52.34397399810784, 5.133348999753408 52.34426400016382, 5.133306000150508 52.3445579987906, 5.133423000650934 52.34430999896628, 5.133606000344242 52.34420899845534, 5.13405899879568 52.34454499931376, 5.134131999834183 52.34473700041124, 5.133972999589881 52.34508699918106, 5.133650999665187 52.34528000012933, 5.133426000228003 52.3452839995326, 5.133286000137593 52.34518699842494, 5.13332100086831 52.34489799905227, 5.133197001354727 52.34488699927707, 5.133186000073016 52.34520199901964, 5.133664000664943 52.34553899972882, 5.133621001062044 52.3458029999986, 5.133065999995521 52.346079998329, 5.133039000969447 52.34634899926905, 5.133171999214236 52.34630299905039, 5.133181000777902 52.34609599877449, 5.133347000035362 52.3460049996042, 5.133743000857583 52.34622399808989, 5.133873999384326 52.34650799962464, 5.133765000588545 52.34681700026223, 5.133432999241161 52.34700199957148, 5.133039000969447 52.34698399942433, 5.133021000674577 52.34666300057695, 5.132936001327801 52.3470629989686, 5.133553999177677 52.34745799952236, 5.134239998770942 52.34751799906867, 5.131750001411646 52.36378899908373, 5.131384999051591 52.36516399930461, 5.131012000651815 52.36583899915544, 5.130141998673757 52.36701299962178, 5.123487999191929 52.37374800036275, 5.122528001404444 52.3750769989468, 5.122160999326342 52.37648099908746, 5.122191000761946 52.37723999915127, 5.122477999955923 52.3783059987332, 5.123212001279663 52.3794699992729, 5.124299000990264 52.38053799855609, 5.125647000727788 52.38133199906268, 5.128195001240249 52.38239999976177, 5.128563000344911 52.38280099941822, 5.127237000338346 52.38429099947288, 5.127314000812941 52.38431699984261, 5.128684000281426 52.38287599955879, 5.128942000731284 52.38288699933393, 5.141106999381683 52.38735200024276, 5.141080000355609 52.38752099910542, 5.139815000246814 52.38888899894931, 5.141386999562501 52.38746399911192, 5.156664001355549 52.39306999973748, 5.157333000512967 52.39362499907739, 5.158557000736907 52.39511699883113, 5.161870001035274 52.3995339998115, 5.12544600073961 52.3861879997045, 5.121345001065356 52.38471799949893, 5.121208000552015 52.38470699972379, 5.121232999860045 52.38478599926759, 5.161452000482091 52.39952300003639, 5.161605998598818 52.39980299933309, 5.168116001386608 52.40221399935125, 5.208095998880228 52.41681099830748, 5.208389999919826 52.41693599948525, 5.208243000816259 52.41718999982781, 5.208689000254537 52.41731299847154, 5.208911999973677 52.41712699931352, 5.210903999560591 52.41784399855757, 5.210801999777968 52.41808799897581, 5.211146999292646 52.4182219988108, 5.211430998909555 52.41803300010041, 5.22520999945198 52.42306899935106, 5.226684001253597 52.42359500018259, 5.227068000935083 52.42364999905795, 5.227294000231291 52.42349599937453, 5.227175999871843 52.42333899872247, 5.211546999550958 52.41763999925263, 5.208259001393083 52.41642599909851, 5.208255998983553 52.41636799925445, 5.216968000077641 52.41643299947037, 5.220508999390258 52.41771800036143, 5.220037000784927 52.41827799895323, 5.220171998747761 52.41836999938951, 5.220540000684885 52.41833399909565, 5.221986000768943 52.41785200019645, 5.222114999577641 52.4179000001162, 5.222813000311641 52.41721700004821, 5.222919999389378 52.4172559984783, 5.222252000090982 52.41794599891814, 5.222422998643555 52.41800999928323, 5.222324001270462 52.41830999984388, 5.221775999217098 52.41897999901884, 5.222011999935995 52.41901099864246, 5.222686001220988 52.41836899953871, 5.223952001188806 52.4177959986378, 5.224595001179172 52.41802599972854, 5.224401000204153 52.41884899873627, 5.224570998897704 52.41917499966648, 5.22847400099331 52.42062899941879, 5.232747998938184 52.42531499990199, 5.232621999706554 52.42532699952786, 5.230219001412078 52.42445099993103, 5.229841000884727 52.42453299902675, 5.229819001153767 52.42467799863668, 5.229975998847562 52.42480000026189, 5.255299000613442 52.43407799966484, 5.382928999476576 52.48906699939067, 5.386248998788099 52.49069099783777, 5.388973999839732 52.49228800031301, 5.389332000354167 52.49230099978956, 5.38945899944482 52.4920419987798, 5.3865150009426 52.49036899914682, 5.386509998815026 52.49024199968512, 5.387820001077227 52.49047699861101, 5.389180998982047 52.49056000038911, 5.39216200076549 52.49043199966039, 5.393103001231545 52.49048599868437, 5.394798000201794 52.49077099864726, 5.396088999477641 52.49121500037835, 5.397458998946126 52.49195799998336, 5.406247000655785 52.49721399892318, 5.406193999630197 52.49731699913328, 5.403447998706627 52.49905799853187, 5.403485999014412 52.49916000030737, 5.406451000221031 52.49736899845561, 5.406635999632385 52.49735599897909, 5.41101399991039 52.4999319998331, 5.409526000082455 52.50105399953623, 5.413020000356083 52.50287699944474, 5.413303999972992 52.50295199958391, 5.413497001088989 52.50312099985973, 5.415238999098228 52.50403099862135, 5.415394999765462 52.50421799904384, 5.415751000561851 52.50440299976474, 5.414785000787767 52.50509499989673, 5.414785000787767 52.50518900003335, 5.417429998955275 52.5033639990077, 5.418689999768956 52.50312900008228, 5.41904000141121 52.50336299915691, 5.418489999639801 52.50384199991349, 5.418578001396106 52.50391999818881, 5.41968800069669 52.50335799848673, 5.420150000711794 52.50355899965276, 5.419635999530125 52.50462999988378, 5.420600999445186 52.5051299989231, 5.420255999930507 52.50536399941387, 5.420342998995329 52.50540999963139, 5.420686998650984 52.50517499928984, 5.42421599965533 52.50700199860034, 5.424337999450869 52.50692600002665, 5.428114999482384 52.50872799881822, 5.426541000448651 52.51001199983988, 5.42658400005155 52.51011399878277, 5.426768999462904 52.51008299915959, 5.428341998637613 52.50878799977922, 5.431807000167122 52.51020299966537, 5.434669998758656 52.51122699982698, 5.437407000951022 52.5138929985763, 5.438105998711583 52.51470299951138, 5.439670999014111 52.51686699951928, 5.440170999337001 52.51712099844163, 5.440391999338095 52.51711699903848, 5.440288999696449 52.51727899894217, 5.440518001402186 52.51774099940184, 5.440464000517576 52.51811199927743, 5.440094998721429 52.51849699848025, 5.439628999270234 52.51872199889721, 5.439041999882524 52.51882899851012, 5.438097999839401 52.51877900030575, 5.436595999152687 52.51828399910521, 5.436705000780931 52.51802099869336, 5.436913999641291 52.51789099967984, 5.436681001331925 52.51790700012491, 5.436529000100782 52.51783899894144, 5.436790000127707 52.51735599878275, 5.437320998912762 52.51673399953714, 5.438160999455215 52.51622599885996, 5.437971000748749 52.51604299925719, 5.43752500131047 52.51633499959071, 5.436730000088961 52.51707399978962, 5.43617900129099 52.51807700025113, 5.43607499895786 52.51868799972169, 5.436599998588778 52.51856299996214, 5.437339999066655 52.51900199960489, 5.436433999331317 52.52006399975998, 5.436087000098594 52.52067499923036, 5.435730999302204 52.52207899933897, 5.435122000183533 52.52208599971071, 5.435103999888662 52.52267899903426, 5.435226999543223 52.52267899903426, 5.435247999415163 52.5221539980617, 5.435721000711977 52.52215799888107, 5.435483000275036 52.52447299901507, 5.435261000414918 52.52499399916807, 5.434838000566623 52.52501599871786, 5.434688998912549 52.52637599972594, 5.434958000644118 52.52895899952376, 5.435369999210701 52.53020299942875, 5.435628999519581 52.5301979987586, 5.435923000559178 52.53077999972272, 5.436346000407474 52.53114899989596, 5.436491999652018 52.53115999967084, 5.439279000460443 52.53278499937423, 5.43972799947579 52.53281299944494, 5.440924000814634 52.53348699942724, 5.440866000493932 52.53402199890674, 5.442349001026753 52.53491799975293, 5.44237300047576 52.53510200062215, 5.442066001268866 52.53538899886841, 5.442129000884682 52.53594799901531, 5.443596000840678 52.53680799956803, 5.444172998805699 52.53687399963349, 5.444741000871979 52.53673999980099, 5.447229001345692 52.53516599956985, 5.447411001179978 52.5351509989756, 5.447749998708058 52.53533199887624, 5.448358000800169 52.53611899899184, 5.454677999216567 52.54627299922929, 5.454522001381795 52.54650499860077, 5.454620998754889 52.54663399917915, 5.453553999057204 52.54697700039861, 5.452996001246076 52.54690599824688, 5.452856001155666 52.54697499928083, 5.453093998760146 52.54713700059998, 5.453036001271906 52.54733099997671, 5.454626000882464 52.54913199890607, 5.454708000652172 52.5498469984337, 5.454558998998097 52.55065399839564, 5.454299998689217 52.55114799974288, 5.456922000099203 52.55354299925884, 5.458888000519066 52.55420699931489, 5.462112999061125 52.55444299950543, 5.462889000128742 52.55444199965464, 5.46323000020733 52.55436899921766, 5.464304998777195 52.55387999995702, 5.463335999426043 52.55341499994761, 5.463582998594189 52.55203299797937, 5.491442999590818 52.55305799940128, 5.493322000945859 52.5532689990736, 5.494712000427262 52.55352899993165, 5.496033001138712 52.55387399943609, 5.497449999646188 52.55436699951608, 5.499180999206177 52.5551769990298, 5.532514998922615 52.57546499957983, 5.555330000201866 52.59033199892011, 5.55726799890417 52.59150100009123, 5.558708999693113 52.59225999871322, 5.561033000627411 52.59326799841679, 5.564022001283037 52.59432299960407, 5.567199000927084 52.59516599985325, 5.572889000352873 52.59615699926069, 5.587411000950242 52.5634599994276, 5.587585998938907 52.56239099890804, 5.589237001279695 52.53240199987383, 5.59130499864972 52.51390499820201, 5.588056000658653 52.51377299948682, 5.591293000341448 52.50003599989395, 5.605870998709011 52.48451999945811, 5.568850000435411 52.47154699910882, 5.568895999615378 52.47149899918945, 5.56541999963662 52.47028099964239, 5.579097000396422 52.45571699871736, 5.585377998646011 52.44857099990269, 5.586389000573503 52.44771099933806, 5.583500999841478 52.44656699865928, 5.581974999705757 52.44566299899427, 5.575646999584715 52.44010900057017, 5.573802998960391 52.43879399849467, 5.571845000245172 52.43780999943461, 5.566637000847402 52.43586299959713, 5.587026001409733 52.41530999990155, 5.587458999848255 52.41531199960316, 5.587998000337953 52.41476599892239, 5.587681999567394 52.41464899938345, 5.588668999213416 52.41365399912837, 5.589057001163455 52.4130169978619, 5.589125999933406 52.41230799883998, 5.588901000496222 52.4113669990254, 5.58906999933075 52.41118699897204, 5.585426000376486 52.41143799834619, 5.583217000224568 52.41135799895191, 5.581542001267236 52.41112199875609, 5.580508999608784 52.41088299900782, 5.579073000947416 52.4104199986896, 5.578014000121913 52.40996799956267, 5.565298999700104 52.40261699870806, 5.564429000554507 52.40230599837156, 5.564251000156313 52.40200599922637, 5.554142000567666 52.39608299916701, 5.538650000760121 52.38576199811313, 5.538293000104709 52.38572799893691, 5.537409000100332 52.38516999862766, 5.537297998754044 52.38499999991412, 5.536764000391921 52.38520599892173, 5.53482399913911 52.38468799830974, 5.534422999021776 52.38478399956597, 5.533981998878611 52.38503199938872, 5.533007000373322 52.38437699938858, 5.512406001373564 52.39571799838919, 5.508496000264799 52.39796499880055, 5.474979000855054 52.42003299911712, 5.448323999928474 52.43563399977404, 5.446229000699914 52.43694199864591, 5.438704999240027 52.44265599869298, 5.434299999935948 52.44008499990227, 5.426889999399419 52.43550499919342, 5.427176998593397 52.43455399804178, 5.423774999512165 52.43256399895387, 5.421007998716656 52.43208299849034, 5.416479999758016 52.42954899842376, 5.410879998974116 52.42656299923042, 5.401792999762209 52.42189799844747, 5.397532999843653 52.41984099943814, 5.387997998642959 52.41541299869675, 5.378987999905647 52.41149399990489, 5.367968001286549 52.40698199894393, 5.35077200094509 52.40020899824248), (5.265708000395822 52.42912799892175, 5.266138999116299 52.42876399941284, 5.266295999642556 52.4286369985337, 5.266138999116299 52.42876399941284, 5.265708000395822 52.42912799892175), (5.272795001148634 52.42459799924256, 5.273381000677322 52.4243269986042, 5.273655998730565 52.4242049983952, 5.273381000677322 52.4243269986042, 5.272795001148634 52.42459799924256), (5.299283999985292 52.41641399947262, 5.306685998817177 52.41428499987373, 5.308870999520089 52.41365299927756, 5.306685998817177 52.41428499987373, 5.299283999985292 52.41641399947262), (5.331198998643746 52.40694299909757, 5.331730000261262 52.40677799963866, 5.331772000005138 52.40676499874572, 5.331730000261262 52.40677799963866, 5.331198998643746 52.40694299909757), (5.341153000256643 52.40266999929819, 5.341661999310737 52.40234599948497, 5.342159000056558 52.40201599915066, 5.341661999310737 52.40234599948497, 5.341153000256643 52.40266999929819)), ((5.293548001379536 52.30686599929408, 5.294238000408892 52.30648799903422, 5.293248001185802 52.3059879985631, 5.292870000658452 52.30630000017134, 5.293102998967818 52.3064500004543, 5.292868000940405 52.30668899879025, 5.292632000221509 52.30677999937733, 5.291949000205312 52.30682300004381, 5.291052999060202 52.30679899937541, 5.29090100066152 52.30669199834271, 5.290856001340575 52.30648699918341, 5.290927999687595 52.306326998976, 5.291145000252598 52.30622899801694, 5.292259998848295 52.30609599944662, 5.29213899891178 52.30576599910734, 5.289797000515073 52.30602299900679, 5.291110999380903 52.30782199971886, 5.291626000421594 52.30768499891285, 5.293548001379536 52.30686599929408)), ((5.24152099993004 52.31327799871541, 5.242078000714608 52.31318599969382, 5.242991999322126 52.31278699831843, 5.242891999257548 52.31268299967075, 5.242587999627724 52.31276299906626, 5.242400000639302 52.31294599867484, 5.241874001149361 52.31307599910877, 5.241331001223573 52.31296599852373, 5.241097000222721 52.31277399884151, 5.241105998953926 52.31259599848704, 5.241211001146079 52.31258999938211, 5.241626999148753 52.3128729996513, 5.241722999777241 52.31264399982381, 5.241519000211994 52.31249800036051, 5.241137000248553 52.31240699977351, 5.241095000504677 52.31216099964957, 5.241185999005588 52.31204399869254, 5.240759999580225 52.31217699867895, 5.240324998591196 52.31250000006215, 5.240323998732173 52.31280699958352, 5.240921999401595 52.31323999871929, 5.24152099993004 52.31327799871541)), ((5.315811001169332 52.29463199880814, 5.323831000683555 52.29491099967484, 5.324644999226496 52.29474799849844, 5.328171000653773 52.29268699862858, 5.328308001167114 52.29248399917047, 5.328201999115938 52.29215799964999, 5.326977998891998 52.29134599898501, 5.327075999238531 52.29128399973652, 5.326322000734336 52.29079699874115, 5.327122001250959 52.29031299871444, 5.327033999494654 52.29025999953959, 5.327130000123141 52.29013999902968, 5.326803000903332 52.29011099910708, 5.325892998899443 52.29064299905453, 5.324352000878383 52.2909549992473, 5.32436199946861 52.29100699857131, 5.316018000311647 52.29265199960105, 5.315669001360877 52.29298100009019, 5.315469001231723 52.29492599885343, 5.31637900040315 52.29561099863616, 5.316585999545465 52.2956759988533, 5.316588999122533 52.29547599894775, 5.315750001271563 52.29493599877789, 5.315694000668907 52.2947689981982, 5.315811001169332 52.29463199880814)), ((5.162889998861505 52.31633599855068, 5.163289999119817 52.31588299815003, 5.163129999016492 52.31576400032386, 5.162949998900253 52.31578400017273, 5.162785999360837 52.31596699978123, 5.161804998868951 52.31589099837282, 5.161346001263377 52.31556899967305, 5.161554000264715 52.31528999880727, 5.161516999815952 52.31517199941568, 5.161069000659627 52.31565399832264, 5.16027099986105 52.3153599982783, 5.160169000078426 52.31498499898765, 5.161467001199894 52.31461999962141, 5.161664998778543 52.31464899954389, 5.161684998791458 52.31452399836411, 5.161477999649144 52.31429499995289, 5.159682000755294 52.31483399885406, 5.159411999164703 52.31500599868734, 5.159330999254017 52.31520799829391, 5.159385000138628 52.31574799987828, 5.159610999434836 52.31591099963791, 5.160692999850323 52.31610299932002, 5.162212000972883 52.31676899910244, 5.162581999795591 52.31668499888747, 5.162889998861505 52.31633599855068)), ((5.105783999655399 52.34596699960829, 5.107843001126682 52.34500099926483, 5.109799000123855 52.34368299903349, 5.109704999213413 52.34362999844286, 5.109228001312969 52.34398899870254, 5.107960998653668 52.34332599847543, 5.107946000768328 52.34303600066807, 5.107836999140083 52.3430429996238, 5.107916999191746 52.3433910001083, 5.107782001228912 52.34362799874123, 5.107503000907117 52.34373000051921, 5.107513999356366 52.34391200027609, 5.107192999290694 52.34417799883136, 5.106504999979383 52.34448299864957, 5.105464999307773 52.34510099992494, 5.104532000546362 52.34548199973494, 5.103975999620816 52.34589799857217, 5.10355999878568 52.34591399901768, 5.103511999887667 52.34571199941208, 5.103430000117959 52.34572099948564, 5.103468000425745 52.34591999812258, 5.102519001087509 52.34637699934058, 5.10220300031695 52.34686799973498, 5.100329001089484 52.34720299932621, 5.102176001290876 52.3469719983821, 5.103371999797258 52.34672099900541, 5.105783999655399 52.34596699960829)), ((5.079637000756351 52.34446499991864, 5.079598000589543 52.34453399812234, 5.080327999644731 52.34531399930575, 5.080772999223987 52.34567199971453, 5.086597999445072 52.34659799894412, 5.087590001218669 52.34694299845976, 5.088249998812421 52.34736399938342, 5.087474000577267 52.34677599929765, 5.086616999598965 52.34650199910352, 5.082172000269055 52.34579900059533, 5.081872999934344 52.3456329998678, 5.080688999736234 52.34526100013137, 5.0798990006423 52.34456199961009, 5.079637000756351 52.34446499991864)), ((5.069273000294915 52.36415699941582, 5.068250000059153 52.36427199925405, 5.067699001261182 52.36457800033833, 5.067599001196605 52.36472999890533, 5.067654998966799 52.36507399856918, 5.068096998968987 52.36539299913023, 5.068574999560916 52.3654669994202, 5.069763999054139 52.36533899868897, 5.070137000286376 52.36510999886335, 5.070286998967012 52.36478600046445, 5.070191001170986 52.36447599997691, 5.069734000450996 52.36420499933598, 5.069273000294915 52.36415699941582)), ((4.98674699883542 52.36511399968283, 4.988145999880487 52.36469899928145, 4.990777000021677 52.36377099893664, 4.990624998790534 52.36365099842811, 4.990194999929081 52.36380299841135, 4.989046000461688 52.36298499865059, 4.989427000566106 52.36277199927035, 4.986624999039881 52.3608090003807, 4.985801998933274 52.36123899996077, 4.985388000648644 52.36092299895196, 4.986301999256163 52.36037599841572, 4.985327000750875 52.36004499964465, 4.98477499926142 52.36035299901454, 4.980436999009248 52.35855599943515, 4.979414998632508 52.35949299843804, 4.979742000684777 52.35963300021159, 4.978420000114304 52.36082299970833, 4.977112000402609 52.36029099976673, 4.976213999539454 52.36109199923155, 4.975393999009915 52.36076999911784, 4.974513001415069 52.3615769991037, 4.974809999199273 52.36170300013338, 4.974462000107527 52.36200700009994, 4.977703999085434 52.36425399910696, 4.977452000622175 52.36442899990757, 4.977879999765584 52.36473899897887, 4.978153000933243 52.36459299951676, 4.978276000587805 52.36465999943483, 4.978581000076652 52.36454799914905, 4.980912000024108 52.36329499913819, 4.983646999665966 52.36442699878971, 4.98354000058823 52.36448599848505, 4.98363399866621 52.36450699818458, 4.983603000204045 52.36470199883388, 4.983509999152625 52.36471799927935, 4.983707999563736 52.36474099868051, 4.983786999756376 52.36420199836732, 4.981267000961475 52.3631709992264, 4.985047000570058 52.36118799907192, 4.985211999968495 52.36117899899837, 4.985508000726139 52.36141499919606, 4.985348000622814 52.36160399932434, 4.987020000003079 52.36273999979564, 4.984977998967644 52.36460299944113, 4.984791999697268 52.36461199951467, 4.983905000115824 52.3642499997037, 4.983825000064162 52.36474900031946, 4.983905000115824 52.36453199870358, 4.984740001363164 52.36487499851663, 4.984604000708846 52.36499699872677, 4.984492999362557 52.3649539994769, 4.984224000463451 52.36521799974587, 4.98469199963269 52.36567299984465, 4.98674699883542 52.36511399968283)), ((5.014145999675967 52.3716870005184, 5.013973001405347 52.37163199881016, 5.013238000222584 52.37165999888158, 5.012504998757867 52.37243699909303, 5.012939999746896 52.37278699927756, 5.01297700019566 52.37299999865746, 5.013141999594097 52.37307199924571, 5.014487999613577 52.37303199954833, 5.014564000229148 52.37295699940763, 5.014701000742489 52.37271999935958, 5.014609999409116 52.37218099904706, 5.014371998972175 52.37178599849484, 5.014145999675967 52.3716870005184)), ((5.021517999904709 52.38411799979067, 5.018382000145516 52.3803879996911, 5.016279999071336 52.37732299809841, 5.014382000394863 52.374169998303, 5.014296001189065 52.37417099815382, 5.014094001341864 52.37424999911396, 5.014202000278623 52.37449299968306, 5.014006999444581 52.37471399928562, 5.01343400091565 52.37501799925157, 5.013673001211614 52.37508199820086, 5.013769998866662 52.37544099987499, 5.014177000970594 52.37573699961818, 5.014466999741639 52.37553099919406, 5.014801000807068 52.37596100018936, 5.014822000679006 52.3764979993837, 5.014295001330042 52.37658799870275, 5.014194001406441 52.37672499950728, 5.014412998857028 52.37744599815911, 5.015047000116191 52.37799999906579, 5.015371999617953 52.378015998095, 5.015412999502807 52.37763299858531, 5.015739998722616 52.37741199898286, 5.015965000992262 52.37746599942401, 5.015872999799866 52.37807099980318, 5.016008000595161 52.37835499850407, 5.015992999877358 52.37866899981039, 5.016701999060608 52.38050199967822, 5.016514000072187 52.38092199933264, 5.016989001087047 52.38133599988215, 5.017120999472812 52.3816439978348, 5.017756000590998 52.3819839980945, 5.017665999116646 52.3824709990829, 5.018341000260663 52.38345299986609, 5.018662000326335 52.38381299855812, 5.018768999404072 52.38377799953109, 5.019567000202649 52.38401099875927, 5.020316999270753 52.38381499825974, 5.020670000490075 52.38386999855165, 5.022684999666972 52.3860849994926, 5.022945999693897 52.38619899947962, 5.023521000773336 52.38683499948175, 5.023696998621023 52.38685099992716, 5.023905000454823 52.38678199889149, 5.021517999904709 52.38411799979067)), ((4.577860999054166 52.4642679989124, 4.574717000422791 52.46394599880391, 4.574202999241122 52.46404800057997, 4.573112999953454 52.46393399917811, 4.572959998863288 52.46403599953795, 4.573112000094431 52.4642030001132, 4.573277999351893 52.46415199922517, 4.573783998828918 52.46420099899538, 4.573887001303026 52.46434999800762, 4.573941999214197 52.46483299958653, 4.573754000225776 52.46537699914521, 4.573900999329343 52.46550599972523, 4.577962998836789 52.46608699942824, 4.578100999209153 52.46603099928625, 4.578767998648526 52.46450199940467, 4.577860999054166 52.4642679989124)), ((5.030467998603276 52.34993200007527, 5.030023998883043 52.34960299958906, 5.02932099885393 52.34989899791724, 5.029189000468164 52.34981599896974, 5.029018998942151 52.34984299919043, 5.030452000858913 52.3515339990074, 5.030766998937987 52.35171299921161, 5.034216999749693 52.35148499923633, 5.03419400015971 52.35136799969623, 5.034018999338583 52.35133099955116, 5.033978999312752 52.35077600020757, 5.03171400139064 52.35042599860562, 5.030467998603276 52.34993200007527)), ((5.419404001079782 52.50951999960726, 5.417731998867055 52.50839499893704, 5.412054000582 52.5051319986247, 5.406451000221031 52.50214799916832, 5.39454499904705 52.49509099845601, 5.394371000917408 52.49510999845354, 5.394356000199606 52.49522599955642, 5.405798998666999 52.50210300021779, 5.414613999402731 52.50687599898976, 5.418248999625791 52.50904699937207, 5.418106999817336 52.50974499860831, 5.418332999113544 52.50973699980198, 5.418476998640044 52.50942899902308, 5.418617998589475 52.50934699992845, 5.419598999081362 52.51002000006243, 5.425330001224467 52.51112599931869, 5.425553000943606 52.51109399842851, 5.425519999930934 52.5109980000066, 5.42531700022471 52.51094599926817, 5.419980999044803 52.50992799962746, 5.419404001079782 52.50951999960726)), ((5.423804001088746 52.51428599941815, 5.426318000729511 52.51379700015444, 5.426383999922394 52.51365899950207, 5.423104000636702 52.51421099927911, 5.422410999197815 52.51293499989704, 5.422205999773546 52.51265799874146, 5.421981000336361 52.51253999793731, 5.417135000889116 52.51162899932611, 5.417038000401607 52.51166499820324, 5.417055000837454 52.51178899811215, 5.421722999886503 52.51265499918909, 5.422016001067078 52.51279499954307, 5.425519000071911 52.51963300033983, 5.425760000085921 52.51971499943432, 5.42583900027856 52.51955599908305, 5.423201001124211 52.5143959985835, 5.423804001088746 52.51428599941815)), ((5.434244999192315 52.52579500002841, 5.434144999127737 52.52549399962203, 5.434051001049758 52.52643199845119, 5.43431100121766 52.52891400057342, 5.434618000424552 52.52983499910624, 5.434709998784487 52.52889699886136, 5.43444099988538 52.52638699950083, 5.434244999192315 52.52579500002841)), ((5.43397300071614 52.53009199899666, 5.43406299935803 52.52895899952376, 5.433784998895257 52.52637400002436, 5.433379999341832 52.52506999915776, 5.43305899927616 52.52508099893266, 5.43297599964743 52.52471699942922, 5.432131999668885 52.52472899905491, 5.43167699866694 52.52016999952208, 5.431587999884073 52.52012899997483, 5.431498001242185 52.52017399892524, 5.431940001244373 52.52473399830885, 5.429801999580451 52.52486399873846, 5.429620999605188 52.52662399812696, 5.42910700125598 52.52667999968465, 5.42898800103751 52.52683099981321, 5.429115999987185 52.52780099953228, 5.429263998949775 52.52789200011621, 5.429743999259749 52.52787899922352, 5.430211001402428 52.52938299857154, 5.432398998849946 52.52935399865003, 5.432592999824966 52.53143299859003, 5.432870000428716 52.5325579992562, 5.436572999562704 52.53763799893215, 5.436792999704775 52.5376879999688, 5.436867000602302 52.53758199879078, 5.433811000894771 52.53349199868118, 5.433038999263244 52.53222799892863, 5.432790000377054 52.53059699870416, 5.43356299903514 52.53056599908108, 5.433685998689702 52.53028099911996, 5.433957999998338 52.53027500001523, 5.43397300071614 52.53009199899666)), ((5.526760000162965 52.5798579983517, 5.526606999072799 52.57985899820248, 5.524374999330898 52.57837099789506, 5.525875000299566 52.57746899936652, 5.527506999654 52.57852999824487, 5.527416001153089 52.5785799992812, 5.527249999063166 52.57847399952007, 5.5271180006774 52.57854899824213, 5.527403000153331 52.57871399911284, 5.527601000564442 52.57860100039625, 5.528121000900247 52.57894500004856, 5.528185000375085 52.57912799964951, 5.527922000630114 52.57930299902799, 5.527726999796071 52.57924999985554, 5.527869999463549 52.57937299849615, 5.52758999928273 52.57943699885961, 5.528090999464643 52.57961499920665, 5.528614999236538 52.57931199910124, 5.529603998600606 52.57983999962143, 5.529910000780937 52.57976499948317, 5.533303001130966 52.57778099954526, 5.533271999836338 52.57758599890247, 5.53239099940903 52.57699899868871, 5.532121000650901 52.57694299854768, 5.532273998908605 52.57685999960313, 5.532078001048001 52.57673299872694, 5.53190100050883 52.5768269988625, 5.531722000251612 52.57671200044432, 5.531741000405505 52.57660299829838, 5.53078199964458 52.57608199814959, 5.530856000542106 52.576024999574, 5.530637000259057 52.57587799884972, 5.530559999784463 52.57592799988607, 5.529492000227756 52.5751809994715, 5.529138999008435 52.57523799946333, 5.523933999187733 52.57826699925172, 5.523964000623337 52.57849899862202, 5.527553998692991 52.5808619986541, 5.527887999758419 52.58086899902578, 5.527845000155519 52.58068799912643, 5.526843999650718 52.5800149989999, 5.526760000162965 52.5798579983517)), ((5.399011001081361 52.64570599932639, 5.399318000288252 52.64539199944943, 5.399945999560817 52.6453970001195, 5.400106999523164 52.64529899916564, 5.399972998586891 52.64504699994993, 5.400030998907592 52.64489400012263, 5.401046000271174 52.6445589991308, 5.401622001209635 52.64445499907232, 5.402248000764153 52.64445099825301, 5.402286001071939 52.64439399967803, 5.401770000172226 52.64433400013451, 5.401347000323931 52.64438899900796, 5.400703000474541 52.64453699958142, 5.399745999431661 52.64493099884977, 5.397108000277312 52.647295999975, 5.396004999989888 52.6481490001411, 5.395439000474115 52.64844799942394, 5.392851999794846 52.65032100032609, 5.388720998684988 52.65308299885326, 5.388211999630895 52.65369799913018, 5.388056998822684 52.65414799995454, 5.387980001180551 52.6548559990988, 5.388233999361855 52.6553989987905, 5.388365000721061 52.65534999902174, 5.388165000591904 52.65495799945559, 5.388158998605307 52.65424899904434, 5.388524000965362 52.65351599938214, 5.390305999000432 52.65217899921751, 5.396180000811015 52.64817599894431, 5.397094999277556 52.6474889994979, 5.399011001081361 52.64570599932639)), ((5.343510999230174 52.67653999918403, 5.335794999345776 52.67798500022078, 5.334868999597523 52.67824899906059, 5.333331001153531 52.67898200013617, 5.332847001407466 52.67931799956073, 5.332529000918862 52.67987799954577, 5.332688001163165 52.68094199937561, 5.333015000382972 52.68207699993926, 5.333153000755336 52.68205399912302, 5.332751000778979 52.68037700013682, 5.332771000791895 52.67964399906141, 5.333311001140615 52.67912399877181, 5.333770998605212 52.67884599918889, 5.334906999905309 52.67832700016621, 5.335505000574731 52.67814799997116, 5.343569999409898 52.67664499909277, 5.343510999230174 52.67653999918403)), ((5.295234998645141 52.69001699896049, 5.295183000311038 52.6900479985828, 5.295664000480034 52.6916059997473, 5.295708999800979 52.69152700020729, 5.295234998645141 52.69001699896049)), ((5.294973998618216 52.69209399914661, 5.295450999351122 52.69205499930199, 5.295465000209902 52.69157899952805, 5.294927999438249 52.68990899949969, 5.294399000371239 52.68999000015747, 5.294274000998632 52.68989799830886, 5.2914909996263 52.68994100038898, 5.290785000020118 52.68990099927737, 5.289136000229837 52.68964999991418, 5.287987000762444 52.68970099938419, 5.28688400047502 52.68989499875658, 5.28761899882532 52.69165599936652, 5.287449999990791 52.69121699973564, 5.288275999674466 52.69098399910246, 5.293014000184951 52.69186000007891, 5.294973998618216 52.69209399914661)), ((5.297370000731995 52.69227799859507, 5.297363998745396 52.69218499972818, 5.300456998901691 52.69190999969813, 5.303146999222605 52.69136099948876, 5.351976001156116 52.678217998022, 5.354783999004016 52.67736999852997, 5.357424000708872 52.676364998392, 5.36010100003003 52.67505300016617, 5.361538001382883 52.67420299955596, 5.3656549988015 52.67161199959462, 5.398570000938195 52.65010999924078, 5.399704999546808 52.64921299856, 5.401594999351099 52.64746499883077, 5.411659999477823 52.63752099998938, 5.411886998633054 52.63734399949473, 5.41260899881606 52.63732900031695, 5.412475000712249 52.63692599814252, 5.412708998880638 52.63670499854905, 5.414128999797644 52.63646899977778, 5.415716999690156 52.63705499871908, 5.416076000063613 52.63694200000352, 5.417447999250145 52.63562299998174, 5.418498001344444 52.63447099911426, 5.418524000511496 52.63431099891509, 5.418261000766526 52.63439700024364, 5.416110000935308 52.63630399890858, 5.415900999242487 52.63628300062616, 5.414237998593427 52.63563499960722, 5.415719999267225 52.63422400056821, 5.416799000105643 52.63461999953874, 5.417061999850613 52.63457299947137, 5.417120000171314 52.63431999898825, 5.41590900094713 52.63385099958162, 5.415816999754734 52.63373499989748, 5.415880999229572 52.63355899925349, 5.416783999387841 52.63268399953603, 5.417369998916529 52.63193699912809, 5.450004001013382 52.60003899921168, 5.455531000758779 52.59457099941867, 5.457029999035962 52.59293799950859, 5.457839001116252 52.59180199907829, 5.458710999979894 52.5902149993844, 5.467912999974181 52.5713710000914, 5.468269000770571 52.57019699966268, 5.468262998783972 52.56946300014004, 5.468090000513353 52.56878599919298, 5.467538998882921 52.56778599970802, 5.466997998675177 52.56721400008753, 5.465798000732705 52.56637099983465, 5.46438199925179 52.56571899940548, 5.463260998669495 52.56540199855601, 5.45589500042735 52.56393999861423, 5.453241000696176 52.5633089992999, 5.451375000340891 52.5627429987837, 5.44843099900621 52.5615969998402, 5.445336998990894 52.56000799902102, 5.443568998982141 52.55884999903517, 5.441991000512318 52.55764299927964, 5.439835001385989 52.5555119986101, 5.437832000517362 52.55273799899884, 5.428813000216383 52.53938199928832, 5.428046000712433 52.53809399886941, 5.427306000234556 52.53652499930876, 5.426700000692953 52.53393599899307, 5.426224999678093 52.52877799962294, 5.426258000690765 52.52803099920285, 5.426995998618134 52.52797599891218, 5.427129999554406 52.52787399996959, 5.427008999617891 52.52682099988911, 5.426839000924339 52.52674099908006, 5.426345999614608 52.52675499840732, 5.425868998881702 52.52391999938811, 5.425530001353622 52.52087799868121, 5.425339999814693 52.52071699862839, 5.425043999057051 52.52073099937188, 5.42491000095324 52.52089099957391, 5.42529899992984 52.52476099994503, 5.425121999390667 52.52718000013848, 5.422708999673503 52.52727699841093, 5.422911999379727 52.52870399791865, 5.423249000022224 52.52914799964709, 5.423879998871856 52.52948199937797, 5.424540999157093 52.52959199995928, 5.42542499916147 52.52962499928393, 5.425546998957008 52.52972199897257, 5.426012001381642 52.53422999902732, 5.426281000280748 52.53551699818001, 5.427184000439017 52.5379529986652, 5.435886999969438 52.55109800012255, 5.438493000802599 52.55492199884196, 5.439322000063342 52.55591700049123, 5.440567000159222 52.55720599934111, 5.442730001131172 52.55893399924696, 5.445128999989556 52.56049699828118, 5.447010001062644 52.56147299851605, 5.449949000269749 52.56270899960844, 5.451642999380975 52.56330100049364, 5.453270999299319 52.56375899871441, 5.455784998940083 52.56436299922513, 5.462447000126554 52.56565699874343, 5.464392000674478 52.56620999978298, 5.465562999872832 52.56685599827497, 5.466183000273214 52.56731999843263, 5.46683699871283 52.56802599930118, 5.467284000842594 52.56879000001232, 5.467503001125642 52.57006199856387, 5.467288000278685 52.5709099994864, 5.458675999249175 52.58861399894677, 5.457284000049729 52.59127599967685, 5.456516000686755 52.59237699966513, 5.45548499874635 52.59360999836675, 5.454468000497185 52.59462999911187, 5.411745998683623 52.63651899939744, 5.407212000570846 52.64084399836132, 5.406275999399904 52.64061699966336, 5.405916999026447 52.64077099934153, 5.40568000128099 52.64098199901094, 5.404303999825906 52.64276399791679, 5.404465999647275 52.64280099947639, 5.403841999810802 52.64351199959043, 5.403882999695655 52.64363899904909, 5.404243999787159 52.64364699927147, 5.404347999287827 52.64354599876528, 5.40482000072562 52.64290799908721, 5.405171999253457 52.6426919987478, 5.405318001330464 52.64236899879753, 5.405822001089444 52.64199999863072, 5.406027000513713 52.64150499885597, 5.406770000568658 52.64104699780835, 5.406852000338366 52.64082099896116, 5.407096999788466 52.64095699990924, 5.398975000491619 52.64883399988589, 5.396089999336663 52.6509209985931, 5.360342000044039 52.67417899888893, 5.358767001151284 52.67503799957224, 5.356784000295574 52.67596199905249, 5.354783999004016 52.67673699952534, 5.352631999313777 52.6774299994894, 5.307478000461619 52.68960399969799, 5.303443999839271 52.69054099865227, 5.302361999423784 52.69071299847541, 5.301063001275756 52.69068799937389, 5.299644000217772 52.69052199865533, 5.296999999076825 52.68991599845503, 5.296973000050751 52.68984199958505, 5.297374000168086 52.68947399927175, 5.298191001120555 52.68952199918947, 5.298521999776455 52.68940499823935, 5.299244999818484 52.689654999168, 5.299867999795934 52.68949099956716, 5.300139001245548 52.68926099990242, 5.29992400039859 52.68850399816105, 5.299559000870996 52.68808499979387, 5.298978000637423 52.68779699887128, 5.298465999173798 52.68724499910926, 5.298087998646448 52.68698599952365, 5.297412000475871 52.68672299911874, 5.297038999243634 52.68599599998107, 5.296643001253875 52.68582599985933, 5.294494001140704 52.68526499860783, 5.294385999371483 52.68515000019161, 5.294468999000213 52.68467299915017, 5.294239000267915 52.68438599807818, 5.293638000021424 52.68429700003045, 5.293530001084665 52.68420699929953, 5.293136999839513 52.68441999866903, 5.292569000605695 52.68455899916847, 5.29209599930888 52.68453399865071, 5.291590999690877 52.68428399913809, 5.291245000317176 52.68399099896151, 5.291381000971494 52.6837689995189, 5.290617001044612 52.68352399926006, 5.29024599953042 52.68307799925706, 5.289746999066553 52.6829210000276, 5.287686000709687 52.68304299881544, 5.287257998733818 52.6833379986936, 5.286567999704461 52.68315699879717, 5.286298000946331 52.68295900002152, 5.285568998917705 52.68281899967127, 5.285663999687169 52.68269599961645, 5.286259000779523 52.68242500040526, 5.286298000946331 52.68221199820326, 5.286606000012247 52.68200799890678, 5.287125000489029 52.68207199926921, 5.28946499916769 52.68180299834331, 5.290117000721722 52.68197399831593, 5.291306000214945 52.68182599915955, 5.291735999076399 52.68187199795957, 5.292048000410867 52.6820989994885, 5.292297999156081 52.6821339985139, 5.293029000902753 52.68204399778295, 5.293529001225643 52.68209400023468, 5.294825999655625 52.68239900003674, 5.294997001040661 52.68250599823068, 5.295863000750167 52.68208799971388, 5.296078998623686 52.68206299919611, 5.29706600110217 52.68228599990577, 5.29722800092354 52.68236799899817, 5.296944001306631 52.68239299951593, 5.296740998767945 52.68259800007938, 5.296685000997751 52.68313900006719, 5.296779998934754 52.68324599967734, 5.297036999525588 52.68338599861136, 5.298684999456848 52.68388799875442, 5.299076000983955 52.68443099844364, 5.299560000730019 52.68465899982325, 5.299343000165016 52.68494699932977, 5.299559000870996 52.68509499848607, 5.29958599989707 52.68529999904943, 5.299937001398347 52.68562999936909, 5.300707000479366 52.68583300023091, 5.300854999441956 52.68598099797097, 5.302152000704401 52.68666399801018, 5.302734000796998 52.68676399866492, 5.302867998900809 52.68698599952365, 5.302623999309731 52.68759399942575, 5.302718000220173 52.68806199897766, 5.30287500074643 52.68820599873085, 5.303177000658209 52.68827599961402, 5.30389399871364 52.68825999916936, 5.30443399906236 52.68874600028364, 5.304487999946971 52.68870499932125, 5.303098000465569 52.68695299878357, 5.30116900049447 52.68476099876334, 5.300372999413938 52.68401299851075, 5.299204999792652 52.68321899945805, 5.297287001103264 52.68227999938497, 5.295070999105725 52.68162099859607, 5.293237999763113 52.68130699872088, 5.292001001371878 52.68119100045381, 5.28937100108971 52.68125499940005, 5.287130999643165 52.6815789991991, 5.285778000610526 52.68194799936362, 5.284559999540722 52.68239900003674, 5.283517999151066 52.68290799913524, 5.281459000512246 52.68430699995431, 5.28143199865371 52.68438999889745, 5.28182400003984 52.68465299930246, 5.282081000630674 52.6846779998202, 5.287244000707499 52.68418099893101, 5.28829899926445 52.68419799922643, 5.289420999705768 52.68431400032588, 5.290454001364219 52.68447999962839, 5.292412999938461 52.68507299893684, 5.294124999344557 52.68595899842173, 5.294854001373184 52.68650199811078, 5.295626000172248 52.68735499827163, 5.296198998701179 52.68833799885865, 5.29636400093208 52.68882099900439, 5.296551000061479 52.68982900010892, 5.295509999530846 52.68985199950889, 5.295897998648423 52.6915209996865, 5.29623499929092 52.69207199959739, 5.296653999703125 52.69210599877198, 5.296680998729199 52.69218599957895, 5.297174000038929 52.69218599957895, 5.297231000500608 52.69274099889283, 5.297399999335137 52.69272899926746, 5.297370000731995 52.69227799859507)))", + "name": "SandboxCity", + "show_on_welcome_map": false, + "target_year": 2020 + }, + "model": "login.casestudy", + "pk": 7 + }, + { + "fields": { + "alias": "", + "casestudy": 7, + "gets_evaluated": false, + "role": "", + "user": 1 + }, + "model": "login.userincasestudy", + "pk": 1 + }, + { + "fields": { + "bibtex_types": "unpublished", + "description": "Unpublished Papers", + "hidden": true, + "order": 8, + "title": "Unpublished" + }, + "model": "publications_bootstrap.type", + "pk": 9 + }, + { + "fields": { + "abstract": "\"\"", + "authors": "R. Sileryte", + "book_title": "\"\"", + "chapter": null, + "citekey": "sandbox2016", + "code": "\"\"", + "country": "\"\"", + "doi": null, + "edition": "\"\"", + "editor": "\"\"", + "external": false, + "image": "\"\"", + "institution": "\"\"", + "isbn": null, + "journal": "Sandbox Journal", + "location": "\"\"", + "month": null, + "note": "\"\"", + "number": null, + "organization": "\"\"", + "pages": "\"\"", + "pdf": "\"\"", + "publisher": "\"\"", + "school": "\"\"", + "section": null, + "series": "\"\"", + "status": "p", + "tags": "\"\"", + "thumbnail": "\"\"", + "title": "SandboxData", + "type": 9, + "url": "\"\"", + "volume": null, + "year": 2016 + }, + "model": "publications_bootstrap.publication", + "pk": 73 + }, + { + "fields": { + "casestudy": 7, + "publication": 73 + }, + "model": "publications.publicationincasestudy", + "pk": 60 + }, + { + "fields": { + "code": "FW", + "name": "Food Waste" + }, + "model": "asmfa.keyflow", + "pk": 3 + }, + { + "fields": { + "casestudy": 7, + "keyflow": 3, + "note": "", + "sustainability_conclusions": "", + "sustainability_statusquo": "" + }, + "model": "asmfa.keyflowincasestudy", + "pk": 32 + }, + { + "fields": { + "name": "Use as fuel" + }, + "model": "asmfa.process", + "pk": 76 + }, + { + "fields": { + "name": "Incineration" + }, + "model": "asmfa.process", + "pk": 7 + }, + { + "fields": { + "name": "Fermentation" + }, + "model": "asmfa.process", + "pk": 87 + }, + { + "fields": { + "keyflow": 32, + "nace": "C-2110", + "name": "Essential oil" + }, + "model": "asmfa.composition", + "pk": 60068 + }, + { + "fields": { + "keyflow": 32, + "nace": "E-3821", + "name": "Biofuel from organic waste" + }, + "model": "asmfa.composition", + "pk": 60067 + }, + { + "fields": { + "keyflow": 32, + "nace": "V-0000", + "name": "Food Waste" + }, + "model": "asmfa.composition", + "pk": 54725 + }, + { + "fields": { + "keyflow": 32, + "level": 3, + "name": "Oranges", + "parent": 4201 + }, + "model": "asmfa.material", + "pk": 4204 + }, + { + "fields": { + "keyflow": 32, + "level": 3, + "name": "Vegetal Waste", + "parent": 4199 + }, + "model": "asmfa.material", + "pk": 4201 + }, + { + "fields": { + "keyflow": 32, + "level": 4, + "name": "Essential Orange oils", + "parent": 4204 + }, + "model": "asmfa.material", + "pk": 4208 + }, + { + "fields": { + "keyflow": 32, + "level": 2, + "name": "Food Waste", + "parent": 4198 + }, + "model": "asmfa.material", + "pk": 4199 + }, + { + "fields": { + "keyflow": 32, + "level": 1, + "name": "Organic Waste", + "parent": null + }, + "model": "asmfa.material", + "pk": 4198 + }, + { + "fields": { + "keyflow": 32, + "level": 4, + "name": "Orange fibers", + "parent": 4204 + }, + "model": "asmfa.material", + "pk": 4207 + }, + { + "fields": { + "keyflow": 32, + "level": 4, + "name": "Orange Peel", + "parent": 4201 + }, + "model": "asmfa.material", + "pk": 4203 + }, + { + "fields": { + "keyflow": 32, + "level": 1, + "name": "Essential Oil", + "parent": null + }, + "model": "asmfa.material", + "pk": 4214 + }, + { + "fields": { + "keyflow": 32, + "level": 1, + "name": "Biofuel", + "parent": null + }, + "model": "asmfa.material", + "pk": 4213 + }, + { + "fields": { + "casestudy": 7, + "name": "Waste companies" + }, + "model": "studyarea.stakeholdercategory", + "pk": 2 + }, + { + "fields": { + "casestudy": 7, + "name": "Goverment" + }, + "model": "studyarea.stakeholdercategory", + "pk": 3 + }, + { + "fields": { + "description": null, + "name": "Altona", + "stakeholder_category": 3 + }, + "model": "studyarea.stakeholder", + "pk": 5 + }, + { + "fields": { + "description": null, + "name": "Stadtreinigung Hamburg", + "stakeholder_category": 2 + }, + "model": "studyarea.stakeholder", + "pk": 4 + }, + { + "fields": { + "description": null, + "name": "BUND", + "stakeholder_category": 3 + }, + "model": "studyarea.stakeholder", + "pk": 18 + }, + { + "fields": { + "description": null, + "name": "NABU", + "stakeholder_category": 3 + }, + "model": "studyarea.stakeholder", + "pk": 6 + }, + { + "fields": { + "code": "E", + "done": false, + "keyflow": 32, + "name": "E WATER SUPPLY; SEWERAGE, WASTE MANAGEMENT AND REMEDIATION ACTIVITIES" + }, + "model": "asmfa.activitygroup", + "pk": 244 + }, + { + "fields": { + "code": "I", + "done": false, + "keyflow": 32, + "name": "I ACCOMMODATION AND FOOD SERVICE ACTIVITIES" + }, + "model": "asmfa.activitygroup", + "pk": 246 + }, + { + "fields": { + "code": "C", + "done": false, + "keyflow": 32, + "name": "C MANUFACTURING" + }, + "model": "asmfa.activitygroup", + "pk": 243 + }, + { + "fields": { + "code": "G", + "done": false, + "keyflow": 32, + "name": "G WHOLESALE AND RETAIL TRADE; REPAIR OF MOTOR VEHICLES AND MOTORCYCLES" + }, + "model": "asmfa.activitygroup", + "pk": 245 + }, + { + "fields": { + "code": "H", + "done": false, + "keyflow": 32, + "name": "TRANSPORTATION AND STORAGE" + }, + "model": "asmfa.activitygroup", + "pk": 301 + }, + { + "fields": { + "code": "V", + "done": false, + "keyflow": 32, + "name": "CONSUMPTION IN HOUSEHOLDS" + }, + "model": "asmfa.activitygroup", + "pk": 247 + }, + { + "fields": { + "activitygroup": 243, + "done": false, + "nace": "C-2110", + "name": "C-2110 Manufacture of basic pharmaceutical products" + }, + "model": "asmfa.activity", + "pk": 1842 + }, + { + "fields": { + "activitygroup": 244, + "done": false, + "nace": "E-3821", + "name": "E-3821 Treatment and disposal of non-hazardous waste" + }, + "model": "asmfa.activity", + "pk": 1844 + }, + { + "fields": { + "activitygroup": 246, + "done": false, + "nace": "I-5610", + "name": "I-5610 Restaurants and mobile food service activities" + }, + "model": "asmfa.activity", + "pk": 1847 + }, + { + "fields": { + "activitygroup": 243, + "done": false, + "nace": "C-1399", + "name": "C-1399 Manufacture of other textile N.E.C" + }, + "model": "asmfa.activity", + "pk": 1841 + }, + { + "fields": { + "activitygroup": 243, + "done": false, + "nace": "C-1030", + "name": "C-1030 Other processing and preserving of fruits and vegetables " + }, + "model": "asmfa.activity", + "pk": 1840 + }, + { + "fields": { + "activitygroup": 245, + "done": false, + "nace": "G-4711", + "name": "G-4711 Retail sale in non-specialised stores with food, beverages or tobacco predominating" + }, + "model": "asmfa.activity", + "pk": 1845 + }, + { + "fields": { + "activitygroup": 245, + "done": false, + "nace": "G-4775", + "name": "G-4775 Retail sale of cosmetic and toilet articles in specialised stores" + }, + "model": "asmfa.activity", + "pk": 2401 + }, + { + "fields": { + "activitygroup": 243, + "done": false, + "nace": "C-1920", + "name": "C-1920 Manufacture of refined petroleum products" + }, + "model": "asmfa.activity", + "pk": 2400 + }, + { + "fields": { + "activitygroup": 245, + "done": false, + "nace": "G-4725", + "name": "G-4725 Retail sale of beverages in specialised stores" + }, + "model": "asmfa.activity", + "pk": 1846 + }, + { + "fields": { + "activitygroup": 244, + "done": false, + "nace": "E-3811", + "name": "E-3811 Collection of non-hazardous waste" + }, + "model": "asmfa.activity", + "pk": 1843 + }, + { + "fields": { + "activitygroup": 244, + "done": false, + "nace": "E-3822", + "name": "E-3822 Treatment and disposal of hazardous waste" + }, + "model": "asmfa.activity", + "pk": 2419 + }, + { + "fields": { + "activitygroup": 301, + "done": false, + "nace": "H-5229", + "name": "H-5229 Other transportation support services" + }, + "model": "asmfa.activity", + "pk": 2418 + }, + { + "fields": { + "activitygroup": 301, + "done": false, + "nace": "H-4941", + "name": "H-4941 Freight transport by road" + }, + "model": "asmfa.activity", + "pk": 2417 + }, + { + "fields": { + "activitygroup": 247, + "done": false, + "nace": "V-0000", + "name": "V-0000 Consumption in households" + }, + "model": "asmfa.activity", + "pk": 1848 + }, + { + "fields": { + "BvDid": "SBC0032", + "BvDii": "LC", + "activity": 2401, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Pharmacy", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 292340 + }, + { + "fields": { + "BvDid": "SBC0030", + "BvDii": "LS", + "activity": 2400, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Petroleum A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 292338 + }, + { + "fields": { + "BvDid": "SBC0031", + "BvDii": "LS", + "activity": 2400, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Petroleum B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 292339 + }, + { + "fields": { + "BvDid": "SBC0016", + "BvDii": "LS", + "activity": 1846, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Beverages A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234470 + }, + { + "fields": { + "BvDid": "SBC0017", + "BvDii": "LS", + "activity": 1846, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Beverages B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234471 + }, + { + "fields": { + "BvDid": "SBC0018", + "BvDii": "LS", + "activity": 1846, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Beverages C", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234472 + }, + { + "fields": { + "BvDid": "SBC0011", + "BvDii": "LS", + "activity": 1845, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Supermarket A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234465 + }, + { + "fields": { + "BvDid": "SBC0012", + "BvDii": "LS", + "activity": 1845, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Supermarket B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234466 + }, + { + "fields": { + "BvDid": "SBC0013", + "BvDii": "LS", + "activity": 1845, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Supermarket C", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234467 + }, + { + "fields": { + "BvDid": "SBC0014", + "BvDii": "LS", + "activity": 1845, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Supermarket D", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234468 + }, + { + "fields": { + "BvDid": "SBC0015", + "BvDii": "LS", + "activity": 1845, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Supermarket E", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234469 + }, + { + "fields": { + "BvDid": "SBC0009", + "BvDii": "LS", + "activity": 1844, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Incinerator", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234463 + }, + { + "fields": { + "BvDid": "SBC0010", + "BvDii": "LS", + "activity": 1844, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Biodigester", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234464 + }, + { + "fields": { + "BvDid": "SBC0007", + "BvDii": "LS", + "activity": 1843, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Waste collector A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234461 + }, + { + "fields": { + "BvDid": "SBC0008", + "BvDii": "LS", + "activity": 1843, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Waste collector B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234462 + }, + { + "fields": { + "BvDid": "SBC0005", + "BvDii": "LS", + "activity": 1842, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Pharma A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234459 + }, + { + "fields": { + "BvDid": "SBC0006", + "BvDii": "LS", + "activity": 1842, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Pharma B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234460 + }, + { + "fields": { + "BvDid": "SBC0003", + "BvDii": "LS", + "activity": 1841, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Textile A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234457 + }, + { + "fields": { + "BvDid": "SBC0004", + "BvDii": "LS", + "activity": 1841, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Textile B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234458 + }, + { + "fields": { + "BvDid": "SBC0001", + "BvDii": "LS", + "activity": 1840, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Farm A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234455 + }, + { + "fields": { + "BvDid": "SBC0002", + "BvDii": "LS", + "activity": 1840, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Farm B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234456 + }, + { + "fields": { + "BvDid": "SBC0037", + "BvDii": "LC", + "activity": 2419, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Hazardous A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 292347 + }, + { + "fields": { + "BvDid": "SBC0038", + "BvDii": "LC", + "activity": 2419, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Hazardous B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 292348 + }, + { + "fields": { + "BvDid": "SBC0035", + "BvDii": "LC", + "activity": 2418, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Railway A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 292345 + }, + { + "fields": { + "BvDid": "SBC0036", + "BvDii": "LC", + "activity": 2418, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Railway B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 292346 + }, + { + "fields": { + "BvDid": "SBC0033", + "BvDii": "LC", + "activity": 2417, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Freight A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 292343 + }, + { + "fields": { + "BvDid": "SBC0034", + "BvDii": "LC", + "activity": 2417, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Freight B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 292344 + }, + { + "fields": { + "BvDid": "SBC0023", + "BvDii": "LS", + "activity": 1848, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Neighbourhood A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234477 + }, + { + "fields": { + "BvDid": "SBC0024", + "BvDii": "LS", + "activity": 1848, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Neighbourhood B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234478 + }, + { + "fields": { + "BvDid": "SBC0025", + "BvDii": "LS", + "activity": 1848, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Neighbourhood C", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234479 + }, + { + "fields": { + "BvDid": "SBC0026", + "BvDii": "LS", + "activity": 1848, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Neighbourhood D", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234480 + }, + { + "fields": { + "BvDid": "SBC0027", + "BvDii": "LS", + "activity": 1848, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Neighbourhood E", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234481 + }, + { + "fields": { + "BvDid": "SBC0028", + "BvDii": "LS", + "activity": 1848, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Neighbourhood F", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234482 + }, + { + "fields": { + "BvDid": "SBC0029", + "BvDii": "LS", + "activity": 1848, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Neighbourhood G", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234483 + }, + { + "fields": { + "BvDid": "SBC0019", + "BvDii": "LS", + "activity": 1847, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Restaurant A", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234473 + }, + { + "fields": { + "BvDid": "SBC0020", + "BvDii": "LS", + "activity": 1847, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Restaurant B", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234474 + }, + { + "fields": { + "BvDid": "SBC0021", + "BvDii": "LS", + "activity": 1847, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Restaurant C", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234475 + }, + { + "fields": { + "BvDid": "SBC0022", + "BvDii": "LS", + "activity": 1847, + "consCode": "U", + "description": "-", + "description_eng": "-", + "done": false, + "employees": 5, + "included": true, + "name": "Restaurant D", + "reason": null, + "turnover": "1000.00", + "turnover_currency": "EUR", + "website": "www.website.com", + "year": 2016 + }, + "model": "asmfa.actor", + "pk": 234476 + }, + { + "fields": { + "amount": 5, + "composition": 54725, + "description": null, + "destination": 234463, + "keyflow": 32, + "origin": 234465, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30808 + }, + { + "fields": { + "amount": 5, + "composition": 54725, + "description": null, + "destination": 234463, + "keyflow": 32, + "origin": 234466, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30809 + }, + { + "fields": { + "amount": 5, + "composition": 54725, + "description": null, + "destination": 234463, + "keyflow": 32, + "origin": 234467, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30810 + }, + { + "fields": { + "amount": 10, + "composition": 54725, + "description": null, + "destination": 234463, + "keyflow": 32, + "origin": 234468, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30811 + }, + { + "fields": { + "amount": 10, + "composition": 54725, + "description": null, + "destination": 234463, + "keyflow": 32, + "origin": 234469, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30812 + }, + { + "fields": { + "amount": 35, + "composition": 54725, + "description": null, + "destination": 234463, + "keyflow": 32, + "origin": 234471, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30814 + }, + { + "fields": { + "amount": 75, + "composition": 60067, + "description": null, + "destination": 292338, + "keyflow": 32, + "origin": 234464, + "process": 76, + "publication": 60, + "waste": false, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 46286 + }, + { + "fields": { + "amount": 200, + "composition": 60067, + "description": null, + "destination": 292339, + "keyflow": 32, + "origin": 234464, + "process": 76, + "publication": 60, + "waste": false, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 46287 + }, + { + "fields": { + "amount": 30, + "composition": 54725, + "description": null, + "destination": 234464, + "keyflow": 32, + "origin": 234470, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30813 + }, + { + "fields": { + "amount": 40, + "composition": 54725, + "description": null, + "destination": 234464, + "keyflow": 32, + "origin": 234472, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30815 + }, + { + "fields": { + "amount": 50, + "composition": 54725, + "description": null, + "destination": 234464, + "keyflow": 32, + "origin": 234465, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30829 + }, + { + "fields": { + "amount": 50, + "composition": 54725, + "description": null, + "destination": 234464, + "keyflow": 32, + "origin": 234466, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30830 + }, + { + "fields": { + "amount": 30, + "composition": 54725, + "description": null, + "destination": 234464, + "keyflow": 32, + "origin": 234467, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30831 + }, + { + "fields": { + "amount": 30, + "composition": 54725, + "description": null, + "destination": 234464, + "keyflow": 32, + "origin": 234468, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30832 + }, + { + "fields": { + "amount": 30, + "composition": 54725, + "description": null, + "destination": 234464, + "keyflow": 32, + "origin": 234469, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30833 + }, + { + "fields": { + "amount": 100, + "composition": 54725, + "description": null, + "destination": 234463, + "keyflow": 32, + "origin": 234461, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30827 + }, + { + "fields": { + "amount": 120, + "composition": 54725, + "description": null, + "destination": 234464, + "keyflow": 32, + "origin": 234462, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30828 + }, + { + "fields": { + "amount": 30, + "composition": 60068, + "description": null, + "destination": 234469, + "keyflow": 32, + "origin": 234460, + "process": 76, + "publication": 60, + "waste": false, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 47659 + }, + { + "fields": { + "amount": 30, + "composition": 60068, + "description": null, + "destination": 292340, + "keyflow": 32, + "origin": 234457, + "process": null, + "publication": 60, + "waste": false, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 46288 + }, + { + "fields": { + "amount": 15, + "composition": 60067, + "description": null, + "destination": 292347, + "keyflow": 32, + "origin": 292338, + "process": 76, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 47662 + }, + { + "fields": { + "amount": 14, + "composition": 60067, + "description": null, + "destination": 292348, + "keyflow": 32, + "origin": 292345, + "process": 76, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 47667 + }, + { + "fields": { + "amount": 70, + "composition": 60067, + "description": null, + "destination": 292345, + "keyflow": 32, + "origin": 292339, + "process": 76, + "publication": 60, + "waste": false, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 47663 + }, + { + "fields": { + "amount": 6, + "composition": 60067, + "description": null, + "destination": 292348, + "keyflow": 32, + "origin": 292346, + "process": 76, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 47668 + }, + { + "fields": { + "amount": 30, + "composition": 60067, + "description": null, + "destination": 292346, + "keyflow": 32, + "origin": 292339, + "process": 76, + "publication": 60, + "waste": false, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 47664 + }, + { + "fields": { + "amount": 12, + "composition": 60067, + "description": null, + "destination": 292347, + "keyflow": 32, + "origin": 292343, + "process": 76, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 47665 + }, + { + "fields": { + "amount": 60, + "composition": 60067, + "description": null, + "destination": 292343, + "keyflow": 32, + "origin": 292338, + "process": 76, + "publication": 60, + "waste": false, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 47660 + }, + { + "fields": { + "amount": 20, + "composition": 60067, + "description": null, + "destination": 292347, + "keyflow": 32, + "origin": 292344, + "process": 76, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 47666 + }, + { + "fields": { + "amount": 100, + "composition": 60067, + "description": null, + "destination": 292344, + "keyflow": 32, + "origin": 292339, + "process": 76, + "publication": 60, + "waste": false, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 47661 + }, + { + "fields": { + "amount": 50, + "composition": 54725, + "description": null, + "destination": 234463, + "keyflow": 32, + "origin": 234477, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30820 + }, + { + "fields": { + "amount": 70, + "composition": 54725, + "description": null, + "destination": 234461, + "keyflow": 32, + "origin": 234478, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30821 + }, + { + "fields": { + "amount": 70, + "composition": 54725, + "description": null, + "destination": 234462, + "keyflow": 32, + "origin": 234479, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30822 + }, + { + "fields": { + "amount": 70, + "composition": 54725, + "description": null, + "destination": 234461, + "keyflow": 32, + "origin": 234480, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30823 + }, + { + "fields": { + "amount": 70, + "composition": 54725, + "description": null, + "destination": 234462, + "keyflow": 32, + "origin": 234481, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30824 + }, + { + "fields": { + "amount": 70, + "composition": 54725, + "description": null, + "destination": 234464, + "keyflow": 32, + "origin": 234482, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30825 + }, + { + "fields": { + "amount": 70, + "composition": 54725, + "description": null, + "destination": 234463, + "keyflow": 32, + "origin": 234483, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30826 + }, + { + "fields": { + "amount": 45, + "composition": 54725, + "description": null, + "destination": 234463, + "keyflow": 32, + "origin": 234473, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30816 + }, + { + "fields": { + "amount": 50, + "composition": 54725, + "description": null, + "destination": 234464, + "keyflow": 32, + "origin": 234474, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30817 + }, + { + "fields": { + "amount": 50, + "composition": 54725, + "description": null, + "destination": 234463, + "keyflow": 32, + "origin": 234475, + "process": 7, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30818 + }, + { + "fields": { + "amount": 50, + "composition": 54725, + "description": null, + "destination": 234464, + "keyflow": 32, + "origin": 234476, + "process": 87, + "publication": 60, + "waste": true, + "year": 2016 + }, + "model": "asmfa.actor2actor", + "pk": 30819 + }, + { + "fields": { + "amount": 5.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234463, + "flow": 30808, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234465, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679627 + }, + { + "fields": { + "amount": 5.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234463, + "flow": 30809, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234466, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679628 + }, + { + "fields": { + "amount": 5.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234463, + "flow": 30810, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234467, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679629 + }, + { + "fields": { + "amount": 10.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234463, + "flow": 30811, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234468, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679630 + }, + { + "fields": { + "amount": 10.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234463, + "flow": 30812, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234469, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679631 + }, + { + "fields": { + "amount": 35.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234463, + "flow": 30814, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234471, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679633 + }, + { + "fields": { + "amount": 30.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234464, + "flow": 30813, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234470, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679632 + }, + { + "fields": { + "amount": 40.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234464, + "flow": 30815, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234472, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679634 + }, + { + "fields": { + "amount": 50.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234464, + "flow": 30829, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234465, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679648 + }, + { + "fields": { + "amount": 50.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234464, + "flow": 30830, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234466, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679649 + }, + { + "fields": { + "amount": 30.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234464, + "flow": 30831, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234467, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679650 + }, + { + "fields": { + "amount": 30.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234464, + "flow": 30832, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234468, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679651 + }, + { + "fields": { + "amount": 30.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234464, + "flow": 30833, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234469, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679652 + }, + { + "fields": { + "amount": 200.0, + "avoidable": false, + "composition_name": "Biofuel from organic waste", + "description": null, + "destination": 292339, + "flow": 46287, + "hazardous": false, + "keyflow": 32, + "material": 4213, + "nace": "E-3821", + "origin": 234464, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": false, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679654 + }, + { + "fields": { + "amount": 75.0, + "avoidable": false, + "composition_name": "Biofuel from organic waste", + "description": null, + "destination": 292338, + "flow": 46286, + "hazardous": false, + "keyflow": 32, + "material": 4213, + "nace": "E-3821", + "origin": 234464, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": false, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679653 + }, + { + "fields": { + "amount": 100.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234463, + "flow": 30827, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234461, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679646 + }, + { + "fields": { + "amount": 120.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234464, + "flow": 30828, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234462, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679647 + }, + { + "fields": { + "amount": 30.0, + "avoidable": false, + "composition_name": "Essential oil", + "description": null, + "destination": 234469, + "flow": 47659, + "hazardous": false, + "keyflow": 32, + "material": 4214, + "nace": "C-2110", + "origin": 234460, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": false, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679617 + }, + { + "fields": { + "amount": 30.0, + "avoidable": false, + "composition_name": "Essential oil", + "description": null, + "destination": 292340, + "flow": 46288, + "hazardous": false, + "keyflow": 32, + "material": 4214, + "nace": "C-2110", + "origin": 234457, + "process": null, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": false, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 678051 + }, + { + "fields": { + "amount": 15.0, + "avoidable": false, + "composition_name": "Biofuel from organic waste", + "description": null, + "destination": 292347, + "flow": 47662, + "hazardous": false, + "keyflow": 32, + "material": 4213, + "nace": "E-3821", + "origin": 292338, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679620 + }, + { + "fields": { + "amount": 70.0, + "avoidable": false, + "composition_name": "Biofuel from organic waste", + "description": null, + "destination": 292345, + "flow": 47663, + "hazardous": false, + "keyflow": 32, + "material": 4213, + "nace": "E-3821", + "origin": 292339, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": false, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679621 + }, + { + "fields": { + "amount": 14.0, + "avoidable": false, + "composition_name": "Biofuel from organic waste", + "description": null, + "destination": 292348, + "flow": 47667, + "hazardous": false, + "keyflow": 32, + "material": 4213, + "nace": "E-3821", + "origin": 292345, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679625 + }, + { + "fields": { + "amount": 30.0, + "avoidable": false, + "composition_name": "Biofuel from organic waste", + "description": null, + "destination": 292346, + "flow": 47664, + "hazardous": false, + "keyflow": 32, + "material": 4213, + "nace": "E-3821", + "origin": 292339, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": false, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679622 + }, + { + "fields": { + "amount": 6.0, + "avoidable": false, + "composition_name": "Biofuel from organic waste", + "description": null, + "destination": 292348, + "flow": 47668, + "hazardous": false, + "keyflow": 32, + "material": 4213, + "nace": "E-3821", + "origin": 292346, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679626 + }, + { + "fields": { + "amount": 60.0, + "avoidable": false, + "composition_name": "Biofuel from organic waste", + "description": null, + "destination": 292343, + "flow": 47660, + "hazardous": false, + "keyflow": 32, + "material": 4213, + "nace": "E-3821", + "origin": 292338, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": false, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679618 + }, + { + "fields": { + "amount": 12.0, + "avoidable": false, + "composition_name": "Biofuel from organic waste", + "description": null, + "destination": 292347, + "flow": 47665, + "hazardous": false, + "keyflow": 32, + "material": 4213, + "nace": "E-3821", + "origin": 292343, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679623 + }, + { + "fields": { + "amount": 100.0, + "avoidable": false, + "composition_name": "Biofuel from organic waste", + "description": null, + "destination": 292344, + "flow": 47661, + "hazardous": false, + "keyflow": 32, + "material": 4213, + "nace": "E-3821", + "origin": 292339, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": false, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679619 + }, + { + "fields": { + "amount": 20.0, + "avoidable": false, + "composition_name": "Biofuel from organic waste", + "description": null, + "destination": 292347, + "flow": 47666, + "hazardous": false, + "keyflow": 32, + "material": 4213, + "nace": "E-3821", + "origin": 292344, + "process": 76, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679624 + }, + { + "fields": { + "amount": 50.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234463, + "flow": 30820, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234477, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679639 + }, + { + "fields": { + "amount": 70.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234461, + "flow": 30821, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234478, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679640 + }, + { + "fields": { + "amount": 70.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234462, + "flow": 30822, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234479, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679641 + }, + { + "fields": { + "amount": 70.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234461, + "flow": 30823, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234480, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679642 + }, + { + "fields": { + "amount": 70.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234462, + "flow": 30824, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234481, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679643 + }, + { + "fields": { + "amount": 70.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234464, + "flow": 30825, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234482, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679644 + }, + { + "fields": { + "amount": 70.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234463, + "flow": 30826, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234483, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679645 + }, + { + "fields": { + "amount": 45.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234463, + "flow": 30816, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234473, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679635 + }, + { + "fields": { + "amount": 50.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234464, + "flow": 30817, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234474, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679636 + }, + { + "fields": { + "amount": 50.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234463, + "flow": 30818, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234475, + "process": 7, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679637 + }, + { + "fields": { + "amount": 50.0, + "avoidable": true, + "composition_name": "Food Waste", + "description": null, + "destination": 234464, + "flow": 30819, + "hazardous": false, + "keyflow": 32, + "material": 4199, + "nace": "V-0000", + "origin": 234476, + "process": 87, + "publication": 60, + "stock": null, + "strategy": null, + "to_stock": false, + "waste": true, + "year": 2016 + }, + "model": "asmfa.fractionflow", + "pk": 679638 + }, + { + "fields": { + "keyflow": 32, + "name": "Test EIS" + }, + "model": "changes.solutioncategory", + "pk": 26 + }, + { + "fields": { + "activities_image": "", + "currentstate_image": "charts/Region_capture_7_LJYRMRL.png", + "description": "

I use the upper two categories of Waste producers.  56.10 and 47.11. It includes two shifted flow and three new flow where two are chains.



", + "documentation": "-", + "effect_image": "charts/Region_capture_8_R2dHtEN.png", + "name": "Simplified Peel Pioneer", + "solution_category": 26 + }, + "model": "changes.solution", + "pk": 89 + }, + { + "fields": { + "a": 0.0, + "b": 0.05, + "documentation": "", + "flow_changes": 262, + "flow_reference": 261, + "is_absolute": false, + "name": "From Restaurants to Other processing and preserving of fruits", + "priority": 0, + "question": null, + "scheme": 3, + "solution": 89 + }, + "model": "changes.solutionpart", + "pk": 59 + }, + { + "fields": { + "a": 0.0, + "b": 0.02, + "documentation": "", + "flow_changes": 276, + "flow_reference": 275, + "is_absolute": false, + "name": "from Restaurants to Manufacture of basic pharmaceutical products", + "priority": 6, + "question": null, + "scheme": 5, + "solution": 89 + }, + "model": "changes.solutionpart", + "pk": 61 + }, + { + "fields": { + "a": 0.0, + "b": 0.02, + "documentation": "", + "flow_changes": 274, + "flow_reference": 273, + "is_absolute": false, + "name": "from Retail sale to Manufacture of basic pharmaceutical products", + "priority": 7, + "question": null, + "scheme": 5, + "solution": 89 + }, + "model": "changes.solutionpart", + "pk": 62 + }, + { + "fields": { + "a": 0.0, + "b": 0.47, + "documentation": "", + "flow_changes": 268, + "flow_reference": 267, + "is_absolute": false, + "name": "from Retail sale to Treatment and disposal", + "priority": 3, + "question": null, + "scheme": 5, + "solution": 89 + }, + "model": "changes.solutionpart", + "pk": 63 + }, + { + "fields": { + "a": 0.0, + "b": 0.01, + "documentation": "", + "flow_changes": 244, + "flow_reference": 243, + "is_absolute": false, + "name": "from Restaurants to Manufacture of textile", + "priority": 4, + "question": null, + "scheme": 5, + "solution": 89 + }, + "model": "changes.solutionpart", + "pk": 65 + }, + { + "fields": { + "a": 0.0, + "b": 0.05, + "documentation": "", + "flow_changes": 264, + "flow_reference": 263, + "is_absolute": false, + "name": "From Retail sale to Other processing and preserving of fruits", + "priority": 1, + "question": null, + "scheme": 3, + "solution": 89 + }, + "model": "changes.solutionpart", + "pk": 60 + }, + { + "fields": { + "a": 0.0, + "b": 0.47, + "documentation": "", + "flow_changes": 266, + "flow_reference": 265, + "is_absolute": false, + "name": "from Restaurants to Treatment and disposal", + "priority": 2, + "question": null, + "scheme": 5, + "solution": 89 + }, + "model": "changes.solutionpart", + "pk": 64 + }, + { + "fields": { + "a": 0.0, + "b": 0.01, + "documentation": "", + "flow_changes": 246, + "flow_reference": 245, + "is_absolute": false, + "name": "from Retail sale to Manufacture of textile", + "priority": 5, + "question": null, + "scheme": 5, + "solution": 89 + }, + "model": "changes.solutionpart", + "pk": 66 + }, + { + "fields": { + "is_absolute": true, + "max_value": 1000000000000.0, + "min_value": 1.0, + "question": "How much is the Orange?", + "select_values": "", + "solution": 89, + "step": 0.1, + "unit": "" + }, + "model": "changes.implementationquestion", + "pk": 22 + }, + { + "fields": { + "coordinating_stakeholder": null, + "date": null, + "keyflow": 32, + "name": "", + "status": 0, + "user": 1 + }, + "model": "changes.strategy", + "pk": 88 + }, + { + "fields": { + "note": "This is a useless note!", + "participants": [ + 4, + 5 + ], + "priority": 0, + "solution": 89, + "strategy": 88 + }, + "model": "changes.solutioninstrategy", + "pk": 160 + }, + { + "fields": { + "implementation": 160, + "question": 22, + "value": 9958.0 + }, + "model": "changes.implementationquantity", + "pk": 36 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2418, + "process": null, + "solution_part": 63 + }, + "model": "changes.affectedflow", + "pk": 120 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2417, + "process": null, + "solution_part": 63 + }, + "model": "changes.affectedflow", + "pk": 119 + }, + { + "fields": { + "destination_activity": 2417, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 63 + }, + "model": "changes.affectedflow", + "pk": 118 + }, + { + "fields": { + "destination_activity": 2418, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 63 + }, + "model": "changes.affectedflow", + "pk": 117 + }, + { + "fields": { + "destination_activity": 2400, + "material": 4213, + "origin_activity": 1844, + "process": null, + "solution_part": 63 + }, + "model": "changes.affectedflow", + "pk": 116 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2418, + "process": null, + "solution_part": 64 + }, + "model": "changes.affectedflow", + "pk": 115 + }, + { + "fields": { + "destination_activity": 2418, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 64 + }, + "model": "changes.affectedflow", + "pk": 114 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2417, + "process": null, + "solution_part": 64 + }, + "model": "changes.affectedflow", + "pk": 113 + }, + { + "fields": { + "destination_activity": 2417, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 64 + }, + "model": "changes.affectedflow", + "pk": 112 + }, + { + "fields": { + "destination_activity": 2400, + "material": 4213, + "origin_activity": 1844, + "process": null, + "solution_part": 64 + }, + "model": "changes.affectedflow", + "pk": 111 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2418, + "process": null, + "solution_part": 60 + }, + "model": "changes.affectedflow", + "pk": 110 + }, + { + "fields": { + "destination_activity": 2418, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 60 + }, + "model": "changes.affectedflow", + "pk": 109 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2417, + "process": null, + "solution_part": 60 + }, + "model": "changes.affectedflow", + "pk": 108 + }, + { + "fields": { + "destination_activity": 2417, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 60 + }, + "model": "changes.affectedflow", + "pk": 107 + }, + { + "fields": { + "destination_activity": 2400, + "material": 4213, + "origin_activity": 1844, + "process": null, + "solution_part": 60 + }, + "model": "changes.affectedflow", + "pk": 106 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2418, + "process": null, + "solution_part": 59 + }, + "model": "changes.affectedflow", + "pk": 105 + }, + { + "fields": { + "destination_activity": 2418, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 59 + }, + "model": "changes.affectedflow", + "pk": 104 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2417, + "process": null, + "solution_part": 59 + }, + "model": "changes.affectedflow", + "pk": 103 + }, + { + "fields": { + "destination_activity": 2417, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 59 + }, + "model": "changes.affectedflow", + "pk": 102 + }, + { + "fields": { + "destination_activity": 2400, + "material": 4213, + "origin_activity": 1844, + "process": null, + "solution_part": 59 + }, + "model": "changes.affectedflow", + "pk": 101 + }, + { + "fields": { + "destination_activity": 1844, + "material": 4199, + "origin_activity": 1845, + "process": null, + "solution_part": 61 + }, + "model": "changes.affectedflow", + "pk": 142 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2417, + "process": null, + "solution_part": 61 + }, + "model": "changes.affectedflow", + "pk": 141 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2418, + "process": null, + "solution_part": 61 + }, + "model": "changes.affectedflow", + "pk": 140 + }, + { + "fields": { + "destination_activity": 2418, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 61 + }, + "model": "changes.affectedflow", + "pk": 139 + }, + { + "fields": { + "destination_activity": 2417, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 61 + }, + "model": "changes.affectedflow", + "pk": 138 + }, + { + "fields": { + "destination_activity": 2400, + "material": 4213, + "origin_activity": 1844, + "process": null, + "solution_part": 61 + }, + "model": "changes.affectedflow", + "pk": 137 + }, + { + "fields": { + "destination_activity": 1845, + "material": 4214, + "origin_activity": 1842, + "process": null, + "solution_part": 61 + }, + "model": "changes.affectedflow", + "pk": 136 + }, + { + "fields": { + "destination_activity": 1844, + "material": 4199, + "origin_activity": 1845, + "process": null, + "solution_part": 62 + }, + "model": "changes.affectedflow", + "pk": 135 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2417, + "process": null, + "solution_part": 62 + }, + "model": "changes.affectedflow", + "pk": 134 + }, + { + "fields": { + "destination_activity": 2419, + "material": 4213, + "origin_activity": 2418, + "process": null, + "solution_part": 62 + }, + "model": "changes.affectedflow", + "pk": 133 + }, + { + "fields": { + "destination_activity": 2418, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 62 + }, + "model": "changes.affectedflow", + "pk": 132 + }, + { + "fields": { + "destination_activity": 2417, + "material": 4213, + "origin_activity": 2400, + "process": null, + "solution_part": 62 + }, + "model": "changes.affectedflow", + "pk": 131 + }, + { + "fields": { + "destination_activity": 2400, + "material": 4213, + "origin_activity": 1844, + "process": null, + "solution_part": 62 + }, + "model": "changes.affectedflow", + "pk": 130 + }, + { + "fields": { + "destination_activity": 1845, + "material": 4214, + "origin_activity": 1842, + "process": null, + "solution_part": 62 + }, + "model": "changes.affectedflow", + "pk": 129 + }, + { + "fields": { + "destination_activity": 2401, + "material": 4214, + "origin_activity": 1841, + "process": null, + "solution_part": 62 + }, + "model": "changes.affectedflow", + "pk": 128 + }, + { + "fields": { + "destination_activity": 1840, + "destination_area": null, + "hazardous": -1, + "material": 4203, + "origin_activity": null, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 262 + }, + { + "fields": { + "destination_activity": 1844, + "destination_area": null, + "hazardous": -1, + "material": 4199, + "origin_activity": 1847, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 261 + }, + { + "fields": { + "destination_activity": 1842, + "destination_area": null, + "hazardous": -1, + "material": 4208, + "origin_activity": null, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 276 + }, + { + "fields": { + "destination_activity": 1840, + "destination_area": null, + "hazardous": -1, + "material": 4203, + "origin_activity": 1847, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 275 + }, + { + "fields": { + "destination_activity": 1842, + "destination_area": null, + "hazardous": -1, + "material": 4208, + "origin_activity": null, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 274 + }, + { + "fields": { + "destination_activity": 1840, + "destination_area": null, + "hazardous": -1, + "material": 4203, + "origin_activity": 1845, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 273 + }, + { + "fields": { + "destination_activity": 1844, + "destination_area": null, + "hazardous": -1, + "material": 4198, + "origin_activity": null, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 268 + }, + { + "fields": { + "destination_activity": 1840, + "destination_area": null, + "hazardous": -1, + "material": 4203, + "origin_activity": 1845, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 267 + }, + { + "fields": { + "destination_activity": 1841, + "destination_area": null, + "hazardous": -1, + "material": 4207, + "origin_activity": null, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 244 + }, + { + "fields": { + "destination_activity": 1840, + "destination_area": null, + "hazardous": -1, + "material": 4203, + "origin_activity": 1847, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 243 + }, + { + "fields": { + "destination_activity": 1840, + "destination_area": null, + "hazardous": -1, + "material": 4203, + "origin_activity": null, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 264 + }, + { + "fields": { + "destination_activity": 1844, + "destination_area": null, + "hazardous": -1, + "material": 4199, + "origin_activity": 1845, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 263 + }, + { + "fields": { + "destination_activity": 1844, + "destination_area": null, + "hazardous": -1, + "material": 4198, + "origin_activity": null, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 266 + }, + { + "fields": { + "destination_activity": 1840, + "destination_area": null, + "hazardous": -1, + "material": 4203, + "origin_activity": 1847, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 265 + }, + { + "fields": { + "destination_activity": 1841, + "destination_area": null, + "hazardous": -1, + "material": 4207, + "origin_activity": null, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 246 + }, + { + "fields": { + "destination_activity": 1840, + "destination_area": null, + "hazardous": -1, + "material": 4203, + "origin_activity": 1845, + "origin_area": null, + "process": null, + "waste": -1 + }, + "model": "changes.flowreference", + "pk": 245 + }, + { + "fields": { + "actor": 292340, + "address": "STRAAT 32", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.745145098405945 52.36544884405141)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 488553 + }, + { + "fields": { + "actor": 292338, + "address": "STRAAT 30", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.717809940085802 52.2572652369623)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 488551 + }, + { + "fields": { + "actor": 292339, + "address": "STRAAT 31", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.71369395999681 52.41165085140589)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 488552 + }, + { + "fields": { + "actor": 234470, + "address": "STRAAT 16", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.87804886838449 52.3565469475924)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419148 + }, + { + "fields": { + "actor": 234471, + "address": "STRAAT 17", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.81582917938428 52.3789056462852)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419149 + }, + { + "fields": { + "actor": 234472, + "address": "STRAAT 18", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.83710681663083 52.3885544759881)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419150 + }, + { + "fields": { + "actor": 234465, + "address": "STRAAT 11", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.86248117100884 52.354296538562)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419143 + }, + { + "fields": { + "actor": 234466, + "address": "STRAAT 12", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.86330413998017 52.3476336577361)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419144 + }, + { + "fields": { + "actor": 234467, + "address": "STRAAT 13", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.88620100849523 52.3721741519645)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419145 + }, + { + "fields": { + "actor": 234468, + "address": "STRAAT 14", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.63300522467911 52.3034053529967)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419146 + }, + { + "fields": { + "actor": 234469, + "address": "STRAAT 15", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.62528129916502 52.2635526655727)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419147 + }, + { + "fields": { + "actor": 234463, + "address": "STRAAT 9", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.85873792001689 52.4067455792041)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419141 + }, + { + "fields": { + "actor": 234464, + "address": "STRAAT 10", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.69880514749284 52.2500629456923)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419142 + }, + { + "fields": { + "actor": 234461, + "address": "STRAAT 7", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.86005514506058 52.4043861728864)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419139 + }, + { + "fields": { + "actor": 234462, + "address": "STRAAT 8", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.72334600609259 52.2365481718288)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419140 + }, + { + "fields": { + "actor": 234459, + "address": "STRAAT 5", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.63608658503753 52.3085639758969)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419137 + }, + { + "fields": { + "actor": 234460, + "address": "STRAAT 6", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.76531998445592 52.3568267916599)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419138 + }, + { + "fields": { + "actor": 234457, + "address": "STRAAT 3", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.75045397755668 52.3371057278517)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419135 + }, + { + "fields": { + "actor": 234458, + "address": "STRAAT 4", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.66671857362181 52.3649974999392)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419136 + }, + { + "fields": { + "actor": 234455, + "address": "STRAAT 1", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.76923473790927 52.3636674135605)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419133 + }, + { + "fields": { + "actor": 234456, + "address": "STRAAT 2", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.7057472878632 52.2955821964574)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419134 + }, + { + "fields": { + "actor": 292347, + "address": "STRAAT 37", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.84256116057744 52.33551025328303)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 488558 + }, + { + "fields": { + "actor": 292348, + "address": "STRAAT 38", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.642871831309185 52.30414224689984)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 488559 + }, + { + "fields": { + "actor": 292345, + "address": "STRAAT 35", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.660093179079105 52.36786322474181)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 488556 + }, + { + "fields": { + "actor": 292346, + "address": "STRAAT 36", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.808826768390798 52.31041380609149)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 488557 + }, + { + "fields": { + "actor": 292343, + "address": "STRAAT 33", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.63645591484536 52.26700127121217)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 488554 + }, + { + "fields": { + "actor": 292344, + "address": "STRAAT 34", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.746995471654104 52.42383523795303)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 488555 + }, + { + "fields": { + "actor": 234477, + "address": "STRAAT 23", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.75695742362102 52.3116351701466)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419155 + }, + { + "fields": { + "actor": 234478, + "address": "STRAAT 24", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.67590833349171 52.3119639823403)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419156 + }, + { + "fields": { + "actor": 234479, + "address": "STRAAT 25", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.77072396135794 52.3393032285153)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419157 + }, + { + "fields": { + "actor": 234480, + "address": "STRAAT 26", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.7250637912072 52.3800554634915)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419158 + }, + { + "fields": { + "actor": 234481, + "address": "STRAAT 27", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.6801877566294 52.3663924975175)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419159 + }, + { + "fields": { + "actor": 234482, + "address": "STRAAT 28", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.76420679183267 52.3726436064146)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419160 + }, + { + "fields": { + "actor": 234483, + "address": "STRAAT 29", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.80538317497446 52.3465746569047)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419161 + }, + { + "fields": { + "actor": 234473, + "address": "STRAAT 19", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.90676775384251 52.3762015265235)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419151 + }, + { + "fields": { + "actor": 234474, + "address": "STRAAT 20", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.89703190406538 52.3724495885783)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419152 + }, + { + "fields": { + "actor": 234475, + "address": "STRAAT 21", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.69160973055883 52.2838494366829)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419153 + }, + { + "fields": { + "actor": 234476, + "address": "STRAAT 22", + "area": null, + "city": "SandboxCity", + "country": "", + "geom": "SRID=4326;POINT (4.73098014631714 52.4155679416213)", + "name": null, + "postcode": "1234XX" + }, + "model": "asmfa.administrativelocation", + "pk": 419154 + } +] diff --git a/repair/js/app-config.js b/repair/js/app-config.js index 7c1f22c87..6a85ba989 100644 --- a/repair/js/app-config.js +++ b/repair/js/app-config.js @@ -47,6 +47,7 @@ function (Session) { solutions: '/api/casestudies/{0}/keyflows/{1}/solutions/', solutionparts: '/api/casestudies/{0}/keyflows/{1}/solutions/{2}/parts', questions: '/api/casestudies/{0}/keyflows/{1}/solutions/{2}/questions', + possibleImplementationAreas: '/api/casestudies/{0}/keyflows/{1}/solutions/{2}/areas', solutionCategories: '/api/casestudies/{0}/keyflows/{1}/solutioncategories/', strategies: '/api/casestudies/{0}/keyflows/{1}/strategies/', solutionsInStrategy: '/api/casestudies/{0}/keyflows/{1}/strategies/{2}/solutions/', @@ -70,6 +71,7 @@ function (Session) { actorStock: '/api/casestudies/{0}/keyflows/{1}/actorstock/', flowIndicators: '/api/casestudies/{0}/keyflows/{1}/flowindicators/', flowFilters: '/api/casestudies/{0}/keyflows/{1}/flowfilters/', + conclusions: '/api/casestudies/{0}/keyflows/{1}/conclusions/', arealevels: '/api/casestudies/{0}/levels/', allareas: '/api/casestudies/{0}/areas/', areas: '/api/casestudies/{0}/levels/{1}/areas/', diff --git a/repair/js/conclusions.js b/repair/js/conclusions.js index 27aa07cbc..a94b479ff 100644 --- a/repair/js/conclusions.js +++ b/repair/js/conclusions.js @@ -1,13 +1,14 @@ require(['models/casestudy', 'views/conclusions/setup-users', 'views/conclusions/manage-notepad', 'views/conclusions/objectives', 'views/conclusions/flow-targets', 'views/conclusions/strategies', - 'views/conclusions/modified-flows', 'views/conclusions/sustainability', + 'views/conclusions/modified-flows', 'views/status-quo/sustainability', + 'views/conclusions/conclusions', 'models/indicator', 'collections/gdsecollection', 'app-config', 'utils/utils', - 'underscore', 'html2canvas', 'viewerjs', 'base', 'viewerjs/dist/viewer.css' + 'underscore', 'base' ], function (CaseStudy, SetupUsersView, SetupNotepadView, EvalObjectivesView, EvalFlowTargetsView, EvalStrategiesView, EvalModifiedFlowsView, - SustainabilityView, Indicator, GDSECollection, appConfig, - utils, _, html2canvas, Viewer) { + SustainabilityView, ConclusionsView, Indicator, GDSECollection, + appConfig, utils, _) { /** * entry point for views on subpages of "Conclusions" menu item * @@ -15,36 +16,9 @@ require(['models/casestudy', 'views/conclusions/setup-users', * @module Conclusions */ - var consensusLevels, sections, modal, objectivesView, flowTargetsView, - strategiesView, modifiedFlowsView, sustainabilityView, keyflowSelect; - - - html2image = function(container, onSuccess){ - html2canvas(container).then(canvas => { - var data = canvas.toDataURL("image/png"); - onSuccess(data); - }); - } - - addConclusion = function(){ - var html = document.getElementById('conclusion-template').innerHTML, - template = _.template(html); - if (!modal) { - modal = document.getElementById('conclusion-modal'); - $(modal).on('shown.bs.modal', function() { - console.log('show') - new Viewer.default(modal.querySelector('img')); - }); - } - html2image(document.getElementById('content'), function(image){ - modal.innerHTML = template({ - consensusLevels: consensusLevels, - sections: sections, - image: image - }); - $(modal).modal('show'); - }) - } + var consensusLevels, sections, objectivesView, flowTargetsView, + strategiesView, modifiedFlowsView, sustainabilityView, keyflowSelect, + keyflows; renderSetup = function(caseStudy){ var usersView = new SetupUsersView({ @@ -68,13 +42,15 @@ require(['models/casestudy', 'views/conclusions/setup-users', caseStudy: caseStudy, el: el, template: 'sustainability-template', - keyflowId: keyflowSelect.value + keyflowId: keyflowSelect.value, + fileAttr: 'sustainability_conclusions' }) }) }; - renderWorkshop = function(caseStudy, keyflowId, keyflowName, objectives, - participants, indicators, strategies, aims){ + renderWorkshop = function(caseStudy, keyflow, objectives, + participants, indicators, strategies, aims, + conclusions){ if (participants.size() === 0){ var warning = document.createElement('h3'); warning.innerHTML = gettext('There are no specified users! Please go to setup mode.') @@ -86,8 +62,7 @@ require(['models/casestudy', 'views/conclusions/setup-users', caseStudy: caseStudy, el: document.getElementById('objectives'), template: 'objectives-template', - keyflowId: keyflowId, - keyflowName: keyflowName, + keyflow: keyflow, users: participants, aims: aims, objectives: objectives @@ -97,8 +72,7 @@ require(['models/casestudy', 'views/conclusions/setup-users', caseStudy: caseStudy, el: document.getElementById('flow-targets'), template: 'flow-targets-template', - keyflowId: keyflowId, - keyflowName: keyflowName, + keyflow: keyflow, users: participants, aims: aims, objectives: objectives, @@ -107,8 +81,7 @@ require(['models/casestudy', 'views/conclusions/setup-users', if (strategiesView) strategiesView.close(); strategiesView = new EvalStrategiesView({ caseStudy: caseStudy, - keyflowId: keyflowId, - keyflowName: keyflowName, + keyflow: keyflow, el: document.getElementById('strategies'), template: 'strategies-template', users: participants, @@ -117,11 +90,10 @@ require(['models/casestudy', 'views/conclusions/setup-users', if (modifiedFlowsView) modifiedFlowsView.close(); modifiedFlowsView = new EvalModifiedFlowsView({ caseStudy: caseStudy, - keyflowId: keyflowId, + keyflow: keyflow, el: document.getElementById('modified-flows'), template: 'modified-flows-template', users: participants, - keyflowName: keyflowName, indicators: indicators, strategies: strategies, objectives: objectives @@ -131,10 +103,9 @@ require(['models/casestudy', 'views/conclusions/setup-users', caseStudy: caseStudy, el: document.getElementById('sustainability'), template: 'sustainability-template', - keyflowId: keyflowId + keyflowId: keyflow.id, + fileAttr: 'sustainability_conclusions' }) - - document.getElementById('add-conclusion').addEventListener('click', addConclusion); }; function render(caseStudy, mode){ @@ -149,7 +120,7 @@ require(['models/casestudy', 'views/conclusions/setup-users', var session = appConfig.session; document.getElementById('keyflow-warning').style.display = 'block'; - function renderKeyflow(keyflowId, keyflowName){ + function renderKeyflow(keyflow){ keyflowSelect.disabled = true; document.getElementById('keyflow-warning').style.display = 'none'; var loader = new utils.Loader(document.getElementById('content'), @@ -165,13 +136,13 @@ require(['models/casestudy', 'views/conclusions/setup-users', }); var indicators = new GDSECollection([], { apiTag: 'flowIndicators', - apiIds: [caseStudy.id, keyflowId], + apiIds: [caseStudy.id, keyflow.id], comparator: 'name', model: Indicator }); var promises = []; promises.push(aims.fetch({ - data: { keyflow: keyflowId }, + data: { keyflow: keyflow.id }, error: alert })); promises.push(users.fetch()); @@ -183,24 +154,25 @@ require(['models/casestudy', 'views/conclusions/setup-users', var participants = users.filterBy({'gets_evaluated' : true}); var strategies = new GDSECollection([], { apiTag: 'strategies', - apiIds: [caseStudy.id, keyflowId] + apiIds: [caseStudy.id, keyflow.id] }); var objectives = new GDSECollection([], { apiTag: 'userObjectives', apiIds: [caseStudy.id], comparator: 'name' }); - var promises = []; - promises.push(strategies.fetch({ - data: { 'user__in': participants.pluck('id').join(',') }, - error: alert - })); - // here we need profile resp. user id (same ids) - // shitty naming, there is a chain of 3 different 'user' models - promises.push(objectives.fetch({ - data: { keyflow: keyflowId, 'user__in': participants.pluck('user').join(',') }, - error: alert - })); + if (participants.size() > 0){ + promises.push(strategies.fetch({ + data: { 'user__in': participants.pluck('id').join(',') }, + error: alert + })); + // here we need profile resp. user id (same ids) + // shitty naming, there is a chain of 3 different 'user' models + promises.push(objectives.fetch({ + data: { keyflow: keyflow.id, 'user__in': participants.pluck('user').join(',') }, + error: alert + })); + } Promise.all(promises).then(function(){ var promises = []; objectives.sort(); @@ -218,7 +190,7 @@ require(['models/casestudy', 'views/conclusions/setup-users', })); }); Promise.all(promises).then(function(){ - renderWorkshop(caseStudy, keyflowId, keyflowName, objectives, + renderWorkshop(caseStudy, keyflow, objectives, participants, indicators, strategies, aims); keyflowSelect.disabled = false; }); @@ -239,17 +211,32 @@ require(['models/casestudy', 'views/conclusions/setup-users', keyflowSelect.selectedIndex = 0; } else { - var keyflowName = keyflowSelect.options[keyflowSelect.selectedIndex].text; - renderKeyflow(parseInt(keyflowSession), keyflowName); + var keyflow = keyflows.get(parseInt(keyflowSession)); + renderKeyflow(keyflow); } } keyflowSelect.addEventListener('change', function(){ var keyflowId = this.value, - keyflowName = this.options[this.selectedIndex].text; + keyflow = keyflows.get(keyflowId); session.set('keyflow', keyflowId); session.save(); - renderKeyflow(keyflowId, keyflowName); + + renderKeyflow(keyflow); + }); + + var conclusionsView = new ConclusionsView({ + caseStudy: caseStudy, + el: document.getElementById('conclusions'), + template: 'conclusions-template', + consensusLevels: consensusLevels, + sections: sections, + keyflows: keyflows + }) + + document.getElementById('add-conclusion').addEventListener('click', function(){ + var keyflowId = keyflowSelect.value; + conclusionsView.addConclusion(keyflowId); }); } @@ -276,7 +263,15 @@ require(['models/casestudy', 'views/conclusions/setup-users', promises.push(sections.fetch()); Promise.all(promises).then(function(){ - render(caseStudy, mode); + consensusLevels.sort(); + sections.sort(); + keyflows = new GDSECollection([], { + apiTag: 'keyflowsInCaseStudy', + apiIds: [caseStudyId] + }); + keyflows.fetch({ + success: function(){ render(caseStudy, mode); } + }) }) } }); diff --git a/repair/js/models/gdsemodel.js b/repair/js/models/gdsemodel.js index 4a2ab7018..3b0feb1b2 100644 --- a/repair/js/models/gdsemodel.js +++ b/repair/js/models/gdsemodel.js @@ -84,10 +84,11 @@ function(Backbone, utils, config) { // remove trailing slash if there is one var url = this.urlRoot().replace(/\/$/, ""); // post to resource if already existing (indicated by id) else create by posting to list view + var method = (options.patch) ? 'PATCH' : (this.id != null) ? 'PUT': 'POST' if (this.id != null) url += '/' + this.id; url += '/'; utils.uploadForm(data, url, { - method: (options.patch) ? 'PATCH' : (this.id != null) ? 'PUT': 'POST', + method: method, success: function(resData, textStatus, jqXHR){ // set attributes corresponding to response for(key in resData){ @@ -104,4 +105,4 @@ function(Backbone, utils, config) { }); return GDSEModel; } -); \ No newline at end of file +); diff --git a/repair/js/status-quo.js b/repair/js/status-quo.js index 510e91149..8d77cc8b1 100644 --- a/repair/js/status-quo.js +++ b/repair/js/status-quo.js @@ -1,14 +1,14 @@ require(['d3', 'models/casestudy', 'views/status-quo/workshop-flows', 'views/status-quo/setup-flows', - 'views/status-quo/objectives', + 'views/status-quo/objectives', 'views/status-quo/sustainability', 'views/status-quo/setup-flow-assessment', 'views/study-area/workshop-maps', 'views/study-area/setup-maps', 'views/status-quo/workshop-flow-assessment', 'app-config', 'utils/overrides', 'base', 'static/css/status-quo.css' ], function (d3, CaseStudy, FlowsWorkshopView, FlowsSetupView, - ChallengesAimsView, FlowAssessmentSetupView, BaseMapView, SetupMapView, - FlowAssessmentWorkshopView, appConfig) { + ChallengesAimsView, SustainabilityView, FlowAssessmentSetupView, BaseMapView, + SetupMapView, FlowAssessmentWorkshopView, appConfig) { /** * entry point for views on subpages of "StatusQuo" menu item @@ -100,6 +100,22 @@ require(['d3', 'models/casestudy', 'views/status-quo/workshop-flows', else { renderWorkshop(caseStudy); } + + var sustainabilityView, + el = document.getElementById('sustainability-content'); + keyflowSelect = el.parentElement.querySelector('select[name="keyflow"]'); + keyflowSelect.disabled = false; + keyflowSelect.selectedIndex = 0; // Mozilla does not reset selects on reload + keyflowSelect.addEventListener('change', function(){ + if (sustainabilityView) sustainabilityView.close(); + sustainabilityView = new SustainabilityView({ + caseStudy: caseStudy, + el: el, + template: 'sustainability-template', + keyflowId: keyflowSelect.value, + fileAttr: 'sustainability_statusquo' + }) + }) }}); } }); diff --git a/repair/js/strategy.js b/repair/js/strategy.js index 80ee93c5a..ef47d41ce 100644 --- a/repair/js/strategy.js +++ b/repair/js/strategy.js @@ -123,7 +123,7 @@ require(['models/casestudy', 'models/gdsemodel', 'collections/gdsecollection', var btn = document.getElementById('build-graph'), note = document.getElementById('graph-note'), clone = btn.cloneNode(true); - note.innerHTML = keyflow.get('graph_build') || '-'; + note.innerHTML = keyflow.get('graph_date') || '-'; btn.parentNode.replaceChild(clone, btn); clone.addEventListener('click', function(){ loader.activate(); diff --git a/repair/js/views/common/baseview.js b/repair/js/views/common/baseview.js index af20df313..77a721dfb 100644 --- a/repair/js/views/common/baseview.js +++ b/repair/js/views/common/baseview.js @@ -50,8 +50,13 @@ var BaseView = Backbone.View.extend( }, /** format a number to currently set language **/ - format: function(value){ - return value.toLocaleString(this.language); + format: function(value, forceSignum){ + var formatted = value.toLocaleString(this.language); + if (this.forceSignum){ + if (value > 0) formatted = '+' + formatted; + if (value == 0) formatted = '+-0'; + } + return formatted; }, /** @@ -100,7 +105,7 @@ var BaseView = Backbone.View.extend( name = model.get('name'); item.text = name.substring(0, 70); if (name.length > 70) item.text += '...'; - item.title = model.get('name'); + item.title = name; item.level = 1; item.id = model.id; item.parent = model.get(parentAttr); @@ -163,6 +168,13 @@ var BaseView = Backbone.View.extend( if (options.onSelect) options.onSelect(model); }) } + select.select = function(id){ + var li = select.querySelector('li[data-value="' + id + '"]'); + if(li){ + var item = li.querySelector('a'); + item.click(); + } + } return select; }, diff --git a/repair/js/views/common/filter-flows.js b/repair/js/views/common/filter-flows.js index 226d4d33e..e60ce998e 100644 --- a/repair/js/views/common/filter-flows.js +++ b/repair/js/views/common/filter-flows.js @@ -179,6 +179,7 @@ var FilterFlowsView = BaseView.extend( displayWarnings: true, filter: filter }); + this.flowsView.loader = this.loader; var displayLevel = this.displayLevelSelect.value; this.flowsView.draw(displayLevel); }, diff --git a/repair/js/views/common/flows.js b/repair/js/views/common/flows.js index fdd22a902..02e276ad2 100644 --- a/repair/js/views/common/flows.js +++ b/repair/js/views/common/flows.js @@ -1,8 +1,9 @@ define(['views/common/baseview', 'underscore', 'views/common/flowsankeymap', - 'collections/gdsecollection', 'views/common/flowsankey', 'utils/utils'], + 'collections/gdsecollection', 'models/gdsemodel', + 'views/common/flowsankey', 'utils/utils', 'backbone'], -function(BaseView, _, FlowMapView, GDSECollection, - FlowSankeyView, utils){ +function(BaseView, _, FlowMapView, GDSECollection, GDSEModel, + FlowSankeyView, utils, Backbone){ /** * * @author Christoph Franke @@ -176,8 +177,40 @@ var FlowsView = BaseView.extend( }, redraw: function(){ + var showDelta = this.modDisplaySelect.value === 'delta', + dblCheck = this.el.querySelector('#sankey-dblclick'), + mapWrapper = this.flowMapView.el.parentElement; + if (dblCheck) dblCheck.parentElement.style.display = (showDelta) ? 'None': 'block'; + if (showDelta) + mapWrapper.classList.add('disabled'); + else + mapWrapper.classList.remove('disabled'); if (!this.displayLevel) return; - this.draw(this.displayLevel) + this.draw(this.displayLevel); + }, + + postprocess: function(flows){ + var idx = 0; + flows.forEach(function(flow){ + var origin = flow.get('origin'), + destination = flow.get('destination'); + // api aggregates flows and doesn't return an id + // generate an internal one to assign interactions + flow.set('id', idx); + idx++; + + // remember original amounts to be able to swap amount with delta and back + flow._amount = flow.get('amount'); + var materials = flow.get('materials'); + flow.get('materials').forEach(function(material){ + material._amount = material.amount; + }) + flow.set('materials', materials); + + origin.color = utils.colorByName(origin.name); + if (!flow.get('stock')) + destination.color = utils.colorByName(destination.name); + }) }, // fetch flows and calls options.success(flows) on success @@ -196,7 +229,6 @@ var FlowsView = BaseView.extend( apiTag: 'flows', apiIds: [ this.caseStudy.id, this.keyflowId] }); - this.loader.activate(); var data = {}; if (options.strategy) @@ -206,27 +238,7 @@ var FlowsView = BaseView.extend( data: data, body: filterParams, success: function(response){ - var idx = 0; - flows.forEach(function(flow){ - var origin = flow.get('origin'), - destination = flow.get('destination'); - // api aggregates flows and doesn't return an id - // generate an internal one to assign interactions - flow.set('id', idx); - idx++; - - // remember original amounts to be able to swap amount with delta and back - flow._amount = flow.get('amount'); - var materials = flow.get('materials'); - flow.get('materials').forEach(function(material){ - material._amount = material.amount; - }) - flow.set('materials', materials); - - origin.color = utils.colorByName(origin.name); - if (!flow.get('stock')) - destination.color = utils.colorByName(destination.name); - }) + _this.postprocess(flows); _this.loader.deactivate(); if (options.success) options.success(flows); @@ -238,6 +250,113 @@ var FlowsView = BaseView.extend( }) }, + calculateDelta: function(statusQuoFlows, strategyFlows){ + var deltaFlows = new Backbone.Collection(null, { model: GDSEModel }); + + function find(flow, collection){ + var originId = flow.get('origin').id, + destination = flow.get('destination'), + destinationId = (destination) ? destination.id: null, + waste = flow.get('waste'), + process_id = flow.get('process_id'), + hazardous = flow.get('hazardous'); + var found = statusQuoFlows.filter(function(model){ + var sqDest = model.get('destination'), + sqDestId = (sqDest) ? sqDest.id: null; + return ((model.get('origin').id == originId) && + (destinationId == sqDestId) && + (model.get('waste') == waste) && + (model.get('process_id') == process_id) && + (model.get('hazardous') == hazardous)); + }); + return found; + } + + function mergeMaterials(statusQuoMaterials, strategyMaterials){ + var sfMats = {}, + sqMats = {}, + materials = []; + if (strategyMaterials) + strategyMaterials.forEach(function(material){ + var key = material.material; + sfMats[key] = material; + }) + statusQuoMaterials.forEach(function(material){ + var key = material.material; + sqMats[key] = material; + }) + for (let [key, sfMaterial] of Object.entries(sfMats)){ + var sqMaterial = sqMats[key], + sfClone = Object.assign({}, sfMaterial); + // material is in both: delta + if (sqMaterial) + sfClone.amount -= sqMaterial.amount; + // material only in strategy: keep as is + materials.push(sfClone); + } + for (let [key, sqMaterial] of Object.entries(sqMats)){ + var sfMaterial = sfMats[key]; + // material not in strategy + if (!sfMaterial){ + var sqClone = Object.assign({}, sfMaterial); + sqClone.amount = -sqClone.amount; + materials.push(sqClone); + } + } + return materials; + } + + function hash(collection){ + var hashed = {}; + collection.forEach(function(model){ + var originId = model.get('origin').id, + destination = model.get('destination'), + destinationId = (destination) ? destination.id: null, + waste = model.get('waste'), + processId = model.get('process_id'), + hazardous = model.get('hazardous'); + var key = originId + '-' + destinationId + '-' + waste + '-' + processId + '-' + hazardous; + hashed[key] = model; + }) + return hashed; + } + + var sfHashed = hash(strategyFlows); + sqHashed = hash(statusQuoFlows); + + for (let [key, strategyFlow] of Object.entries(sfHashed)) { + var statusQuoFlow = sqHashed[key], + deltaFlow = strategyFlow.clone(); + + // strategy flow already existed in status quo + if (statusQuoFlow){ + var mergedMaterials = mergeMaterials(statusQuoFlow.get('materials'), strategyFlow.get('materials')); + deltaFlow.set('materials', mergedMaterials); + deltaFlow.set('amount', strategyFlow.get('amount') - statusQuoFlow.get('amount')); + } + // strategy flow is completely new + else { + deltaFlow.set('amount', strategyFlow.get('amount')); + } + deltaFlows.add(deltaFlow); + } + + for (let [key, statusQuoFlow] of Object.entries(sqHashed)) { + var strategyFlow = sfHashed[key]; + + // status quo flow does not exist in strategy anymore + if (!strategyFlow){ + deltaFlow = statusQuoFlow.clone(); + deltaFlow.set('amount', -statusQuoFlow.get('amount')); + var mergedMaterials = mergeMaterials(statusQuoFlow.get('materials'), null); + deltaFlow.set('materials', mergedMaterials); + var materials = statusQuoFlow.get('materials'); + deltaFlows.add(deltaFlow); + } + } + return deltaFlows; + }, + draw: function(displayLevel){ this.flowMem = {}; if (this.flowMapView != null) this.flowMapView.clear(); @@ -251,15 +370,16 @@ var FlowsView = BaseView.extend( _this = this; function drawSankey(){ - var flows = (_this.strategy && _this.modDisplaySelect.value != 'statusquo') ? _this.strategyFlows : _this.flows; + var modDisplay = _this.modDisplaySelect.value, + flows = (modDisplay == 'statusquo') ? _this.flows : (modDisplay == 'strategy') ? _this.strategyFlows : _this.deltaFlows; // override value and color flows.forEach(function(flow){ - var amount = (showDelta) ? flow.get('delta') : flow._amount; - flow.color = (!showDelta) ? null: (amount >= 0) ? '#23FE01': 'red'; + var amount = flow._amount; + flow.color = (!showDelta) ? null: (amount > 0) ? '#23FE01': 'red'; flow.set('amount', amount) var materials = flow.get('materials'); flow.get('materials').forEach(function(material){ - material.amount = (showDelta) ? material.delta : material._amount; + material.amount = material._amount; }) flow.set('materials', materials); }); @@ -287,6 +407,8 @@ var FlowsView = BaseView.extend( displayLevel: displayLevel, success: function(strategyFlows){ _this.strategyFlows = strategyFlows; + _this.deltaFlows = _this.calculateDelta(_this.flows, strategyFlows); + _this.postprocess(_this.deltaFlows); drawSankey(); } }) @@ -353,12 +475,12 @@ var FlowsView = BaseView.extend( function addFlows(flows){ // override value and color flows.forEach(function(flow){ - var amount = (showDelta) ? flow.get('delta') : flow._amount; + var amount = flow._amount; flow.color = (!showDelta) ? null: (amount > 0) ? '#23FE01': 'red'; flow.set('amount', amount) var materials = flow.get('materials'); flow.get('materials').forEach(function(material){ - material.amount = (showDelta) ? material.delta : material._amount; + material.amount = material._amount; }) flow.set('materials', materials); }); @@ -388,7 +510,10 @@ var FlowsView = BaseView.extend( f._amount = f.get('amount'); var materials = f.get('materials'); f.get('materials').forEach(function(material){ - material._amount = material.amount; + // ToDo: change filter API response + // workaround: show statusquo if amount is null + if (material.amount == null) material.amount = material.statusquo_amount; + material._amount = material.amount; }) f.set('materials', materials); }) @@ -411,7 +536,10 @@ var FlowsView = BaseView.extend( linkSelected: function(e){ // only actors atm var data = e.detail, - _this = this; + _this = this, + showDelta = this.modDisplaySelect.value === 'delta'; + + if (showDelta) return; if (!Array.isArray(data)) data = [data]; var promises = []; diff --git a/repair/js/views/common/flowsankey.js b/repair/js/views/common/flowsankey.js index ec86af9a3..ac82d38a4 100644 --- a/repair/js/views/common/flowsankey.js +++ b/repair/js/views/common/flowsankey.js @@ -1,9 +1,9 @@ define(['views/common/baseview', 'underscore', 'visualizations/sankey', 'collections/gdsecollection', 'd3', 'app-config', 'save-svg-as-png', - 'file-saver', 'utils/utils'], + 'file-saver', 'openlayers', 'utils/utils'], function(BaseView, _, Sankey, GDSECollection, d3, config, saveSvgAsPng, - FileSaver, utils, Slider){ + FileSaver, ol, utils){ /** * @@ -50,6 +50,7 @@ var FlowSankeyView = BaseView.extend( this.originLevel = options.originLevel; this.destinationLevel = options.destinationLevel; this.flows = options.flows; + this.renderStocks = (options.renderStocks == null) ? true: options.renderStocks; this.showRelativeComposition = (options.showRelativeComposition == null) ? true : options.showRelativeComposition; this.forceSignum = options.forceSignum || false; this.stretchInput = this.el.querySelector('#sankey-stretch'); @@ -69,6 +70,7 @@ var FlowSankeyView = BaseView.extend( 'click .export-csv': 'exportCSV', 'click .select-all': 'selectAll', 'click .deselect-all': 'deselectAll', + 'change #sankey-dblclick': 'changeDblClick', 'change #sankey-alignment': 'alignSankey', 'change #sankey-scale': 'scale', 'change #sankey-stretch': 'stretch' @@ -95,6 +97,8 @@ var FlowSankeyView = BaseView.extend( } this.el.classList.remove('disabled'); this.sankeyDiv = div; + + var dblclkCheck = this.el.querySelector('#sankey-dblclick'); this.sankey = new Sankey({ height: height, width: width, @@ -103,7 +107,9 @@ var FlowSankeyView = BaseView.extend( language: config.session.get('language'), selectable: true, gradient: false, - stretchFactor: (this.stretchInput) ? this.stretchInput.value: 1 + stretchFactor: (this.stretchInput) ? this.stretchInput.value: 1, + selectOnDoubleClick: (dblclkCheck) ? dblclkCheck.checked : false, + forceSignum: this.forceSignum }) // redirect the event with same properties @@ -125,17 +131,27 @@ var FlowSankeyView = BaseView.extend( //this.render(this.transformedData); }, + changeDblClick: function(evt){ + this.deselectAll(); + var checked = evt.target.checked; + this.sankey.selectOnDoubleClick = checked; + this.sankey.render(this.transformedData); + }, + alignSankey: function(evt){ + this.deselectAll(); this.sankey.align(evt.target.value); this.sankey.render(this.transformedData); }, scale: function(){ + this.deselectAll(); this.transformedData = this.transformData(this.flows); this.render(this.transformedData); }, stretch: function(evt){ + this.deselectAll(); this.sankey.stretch(evt.target.value) this.sankey.render(this.transformedData) }, @@ -168,7 +184,8 @@ var FlowSankeyView = BaseView.extend( var id = node.id, name = node.name, level = node.level, - code = node.code || node.nace || node.activity__nace; + code = node.code || node.nace || node.activity__nace, + geom = node.geom, key = level + id; if ((_this.anonymize) && (level === 'actor')) name = gettext('Actor'); @@ -177,7 +194,7 @@ var FlowSankeyView = BaseView.extend( return indices[key]; idx += 1; var color = node.color || utils.colorByName(name); - nodes.push({ id: id, name: name + ' (' + code + ')', color: color, code: code }); + nodes.push({ id: id, name: name + ' (' + code + ')', color: color, code: code, geom: geom }); indices[key] = idx; return idx; } @@ -195,16 +212,14 @@ var FlowSankeyView = BaseView.extend( fractions = flow.get('materials'), totalAmount = flow.get('amount'); fractions.forEach(function(material){ - var amount = (material.value != null) ? material.value : material.amount || material.statusquo_amount; + var amount = (material.value != null) ? material.value: material.amount; if (amount == 0) return; - if (_this.forceSignum && amount >= 0) - text += '+'; if (_this.showRelativeComposition){ fraction = amount / totalAmount, value = Math.round(fraction * 100000) / 1000; - text += _this.format(value) + '%'; + text += _this.format(value, _this.forceSignum) + '%'; } else { - text += _this.format(amount) + ' ' + gettext('t/year'); + text += _this.format(amount, _this.forceSignum) + ' ' + gettext('t/year'); } text += ' ' + material.name if (material.avoidable) text += ' ' + gettext('avoidable') +''; @@ -227,7 +242,6 @@ var FlowSankeyView = BaseView.extend( maxAmount = Math.max(...amounts), max = 10000, normFactor = max / maxAmount; - flows.forEach(function(flow){ var value = flow.get('amount'); // skip flows with zero amount @@ -235,21 +249,21 @@ var FlowSankeyView = BaseView.extend( var origin = flow.get('origin'), destination = flow.get('destination'), isStock = flow.get('stock'); + if (isStock && !_this.renderStocks) return; if (!isStock && origin.id == destination.id) { console.log('Warning: self referencing cycle at node ' + origin.name); return; } function normalize(v){ - return Math.log2(1 + v * normFactor); + var normed = Math.log2(1 + Math.abs(v) * normFactor); + if (v < 0) normed *= -1; + return normed; } var source = mapNode(origin), target = (!isStock) ? mapNode(destination) : addStock(); var crepr = compositionRepr(flow), amount = flow.get('amount'), value = (norm === 'log')? normalize(amount): Math.round(amount); - - if (_this.forceSignum && amount >= 0) - amount = '+' + amount.toLocaleString(this.language); links.push({ id: flow.id, originalData: flow, @@ -305,26 +319,37 @@ var FlowSankeyView = BaseView.extend( exportCSV: function(){ if (!this.transformedData) return; - var header = [gettext('origin'), gettext('origin_code'), - gettext('destination'), gettext('destination_code'), - gettext('amount'), gettext('composition')], + var header = [gettext('origin'), gettext('origin') + '_code', gettext('origin') + '_wkt', + gettext('destination'), gettext('destination') + '_code', gettext('destination') + '_wkt', + gettext('amount (t/year)'), gettext('composition')], rows = [], _this = this; rows.push(header.join('\t')); + var geoJSON = new ol.format.GeoJSON(), + wkt = new ol.format.WKT(); + + function geomToWkt(geom){ + var geometry = geoJSON.readGeometry(geom); + return wkt.writeGeometry(geometry) + } + this.transformedData.links.forEach(function(link){ var origin = link.source, destination = link.target, originName = origin.name, destinationName = (!link.isStock) ? destination.name : gettext('Stock'), - amount = _this.format(link.amount) + ' ' + link.units, + amount = _this.format(link.amount, _this.forceSignum), composition = link.composition; - if (_this.forceSignum && amount >= 0) - amount = '-' + amount; var originCode = origin.code, - destinationCode = (destination) ? destination.code: ''; + destinationCode = (destination) ? destination.code: '', + originWkt = '', + destinationWkt = ''; + + var originWkt = (origin.geom) ? geomToWkt(origin.geom) : '', + destinationWkt = (destination.geom) ? geomToWkt(destination.geom) : ''; - var row = [originName, originCode, destinationName, destinationCode, amount, composition]; + var row = [originName, originCode, originWkt, destinationName, destinationCode, destinationWkt, amount, composition]; rows.push(row.join('\t')); }); var text = rows.join('\r\n'); diff --git a/repair/js/views/common/flowsankeymap.js b/repair/js/views/common/flowsankeymap.js index 07a60a0c5..5ac6a4524 100644 --- a/repair/js/views/common/flowsankeymap.js +++ b/repair/js/views/common/flowsankeymap.js @@ -84,7 +84,8 @@ function(_, BaseView, GDSECollection, GeoLocations, Flows, FlowMap, ol, utils, L position: 'topright', filename: 'sankey-map', exportOnly: true, - hideControlContainer: true + hideControlContainer: true, + sizeModes: ['A4Landscape'] })); this.leafletMap.on("zoomend", this.zoomed); @@ -103,7 +104,7 @@ function(_, BaseView, GDSECollection, GeoLocations, Flows, FlowMap, ol, utils, L // easyprint is not customizable enough (buttons, remove menu etc.) and not touch friendly // workaround: hide it and pass on clicks (actually strange, but easyprint was still easiest to use export plugin out there) var easyprintCtrl = this.el.querySelector('.leaflet-control-easyPrint'), - easyprintCsBtn = this.el.querySelector('.easyPrintHolder .CurrentSize'); + easyprintCsBtn = this.el.querySelector('.easyPrintHolder .A4Landscape'); easyprintCtrl.style.visibility = 'hidden'; exportImgBtn.addEventListener('click', function(){ easyprintCsBtn.click(); @@ -157,10 +158,8 @@ function(_, BaseView, GDSECollection, GeoLocations, Flows, FlowMap, ol, utils, L aniDiv = document.createElement('div'), aniCheckWrap = document.createElement('div'), aniToggleDiv = document.createElement('div'), - toggleAniBtn = document.createElement('button'), clusterDiv = document.createElement('div'); - toggleAniBtn.classList.add('glyphicon', 'glyphicon-chevron-right'); matDiv.appendChild(this.materialCheck); matDiv.appendChild(matLabel); matDiv.style.cursor = 'pointer'; @@ -180,25 +179,27 @@ function(_, BaseView, GDSECollection, GeoLocations, Flows, FlowMap, ol, utils, L aniCheckWrap.appendChild(this.animationCheck); aniCheckWrap.appendChild(aniLabel); aniDiv.appendChild(aniCheckWrap); - aniDiv.appendChild(toggleAniBtn); aniCheckWrap.style.cursor = 'pointer'; - aniToggleDiv.style.visibility = 'hidden'; var aniLinesLabel = document.createElement('label'), aniDotsLabel = document.createElement('label'); this.aniLinesRadio = document.createElement('input'); - this.aniLinesRadio.checked = true; this.aniDotsRadio = document.createElement('input'); this.aniLinesRadio.type = 'radio'; this.aniDotsRadio.type = 'radio'; this.aniLinesRadio.name = 'animation'; this.aniDotsRadio.name = 'animation'; + this.aniLinesRadio.style.transform = 'scale(1.5)'; + this.aniLinesRadio.style.marginLeft = '5px'; + this.aniDotsRadio.style.transform = 'scale(1.5)'; + this.aniDotsRadio.style.marginLeft = '5px'; + + this.aniDotsRadio.checked = true; aniCheckWrap.style.float = 'left'; aniCheckWrap.style.marginRight = '5px'; aniToggleDiv.style.float = 'left'; - toggleAniBtn.style.float = 'left'; aniLinesLabel.style.marginRight = '3px'; aniLinesLabel.innerHTML = 'lines only'; @@ -239,9 +240,6 @@ function(_, BaseView, GDSECollection, GeoLocations, Flows, FlowMap, ol, utils, L _this.animationCheck.checked = !_this.animationCheck.checked; _this.flowMap.toggleAnimation(_this.animationCheck.checked); }); - toggleAniBtn.addEventListener("click", function(){ - aniToggleDiv.style.visibility = (aniToggleDiv.style.visibility == 'hidden') ? 'visible': 'hidden'; - }); aniToggleDiv.addEventListener("click", function(){ if (_this.aniDotsRadio.checked) _this.aniLinesRadio.checked = true; @@ -272,6 +270,8 @@ function(_, BaseView, GDSECollection, GeoLocations, Flows, FlowMap, ol, utils, L legendControl.onAdd = function () { return _this.legend; }; legendControl.addTo(this.leafletMap); this.el.querySelector('.leaflet-right.leaflet-bottom').classList.add('leaflet-legend'); + L.DomEvent.disableClickPropagation(this.legend); + L.DomEvent.disableScrollPropagation(this.legend); }, toggleMaterials(){ @@ -293,20 +293,22 @@ function(_, BaseView, GDSECollection, GeoLocations, Flows, FlowMap, ol, utils, L text = document.createElement('div'), check = document.createElement('input'), colorDiv = document.createElement('div'); - div.style.height = '25px'; + div.style.height = '30px'; div.style.cursor = 'pointer'; text.innerHTML = material.name; text.style.fontSize = '1.3em'; text.style.overflow = 'hidden'; text.style.whiteSpace = 'nowrap'; text.style.textOverflow = 'ellipsis'; - colorDiv.style.width = '20px'; + colorDiv.style.width = '25px'; colorDiv.style.height = '100%'; colorDiv.style.textAlign = 'center'; colorDiv.style.background = color; colorDiv.style.float = 'left'; + colorDiv.style.paddingTop = '5px'; check.type = 'checkbox'; check.checked = _this.showMaterials[matId] === true; + check.style.transform = 'scale(1.7)'; check.style.pointerEvents = 'none'; div.appendChild(colorDiv); div.appendChild(text); @@ -677,7 +679,7 @@ function(_, BaseView, GDSECollection, GeoLocations, Flows, FlowMap, ol, utils, L flowLabel = source.name + '➔ ' + target.name + '
' + wasteLabel+ '
' + processLabel ; if(splitByComposition){ - var cl = [] + var cl = []; fractions.forEach(function(material){ var amount = Math.round(material.amount), label = flowLabel + '
Material: ' + material.name + diff --git a/repair/js/views/conclusions/conclusions.js b/repair/js/views/conclusions/conclusions.js new file mode 100644 index 000000000..7e304b71d --- /dev/null +++ b/repair/js/views/conclusions/conclusions.js @@ -0,0 +1,217 @@ +define(['underscore','views/common/baseview', 'collections/gdsecollection', + 'models/gdsemodel', 'html2canvas', 'muuri', 'viewerjs', 'viewerjs/dist/viewer.css'], + +function(_, BaseView, GDSECollection, GDSEModel, html2canvas, Muuri, Viewer){ + + function html2image(container, onSuccess){ + html2canvas(container).then(canvas => { + var data = canvas.toDataURL("image/png"); + onSuccess(data); + }); + }; + + // source: https://stackoverflow.com/questions/16968945/convert-base64-png-data-to-javascript-file-objects + function dataURLtoFile(dataurl, filename) { + var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], + bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); + while(n--){ + u8arr[n] = bstr.charCodeAt(n); + } + return new File([u8arr], filename, {type:mime}); + }; + + /** + * + * @author Christoph Franke + * @name module:views/ConclusionsView + * @augments Backbone.View + */ + var ConclusionsView = BaseView.extend( + /** @lends module:views/ConclusionsView.prototype */ + { + + /** + * render setup view on challenges and aims + * + * @param {Object} options + * @param {HTMLElement} options.el element the view will be rendered in + * @param {string} options.template id of the script element containing the underscore template to render this view + * @param {module:models/CaseStudy} options.caseStudy the casestudy to add challenges and aims to + * + * @constructs + * @see http://backbonejs.org/#View + */ + initialize: function(options){ + ConclusionsView.__super__.initialize.apply(this, [options]); + var _this = this; + + this.consensusLevels = options.consensusLevels; + this.sections = options.sections; + this.keyflows = options.keyflows; + this.caseStudy = options.caseStudy; + this.conclusionsInCasestudy = {}; + var promises = []; + this.loader.activate(); + this.keyflows.forEach(function(keyflow){ + var conclusions = new GDSECollection([], { + apiTag: 'conclusions', + apiIds: [_this.caseStudy.id, keyflow.id] + }); + _this.conclusionsInCasestudy[keyflow.id] = conclusions; + promises.push(conclusions.fetch({error: alert})); + }) + Promise.all(promises).then(function(){ + _this.render(); + _this.loader.deactivate(); + }) + }, + + /* + * dom events (managed by jquery) + */ + events: { + //'click .add-challenge-button': 'addChallenge', + }, + + addConclusion: function(keyflowId){ + var html = document.getElementById('add-conclusion-template').innerHTML, + template = _.template(html), + content = document.getElementById('content'), + _this = this; + + if (!this.addModal) { + this.addModal = document.getElementById('add-conclusion-modal'); + $(this.addModal).on('shown.bs.modal', function() { + new Viewer.default(_this.addModal.querySelector('img')); + }); + } + + html2image(content, function(dataURL){ + _this.addModal.innerHTML = template({ + consensusLevels: _this.consensusLevels, + sections: _this.sections, + image: dataURL + }); + $(_this.addModal).modal('show'); + + _this.addModal.querySelector('.btn.confirm').addEventListener('click', function(){ + var step = content.querySelector('.tab-pane.active').dataset.step, + conclusions = _this.conclusionsInCasestudy[keyflowId]; + + var data = { + "consensus_level": _this.addModal.querySelector('select[name="consensus"]').value, + "section": _this.addModal.querySelector('select[name="section"]').value, + "step": step, + "text": _this.addModal.querySelector('textarea[name="comment"]').value + } + + var image = dataURLtoFile(dataURL, 'screenshot.png'); + if (_this.addModal.querySelector('input[name="screenshot"]').checked) { + data.image = image; + } + + var conclusion = new GDSEModel( {}, { apiTag: 'conclusions', apiIds: [ _this.caseStudy.id, keyflowId ] }); + conclusion.save(data, { + success: function(){ + conclusions.add(conclusion); + //$(_this.addModal).modal('close'); + var grid = _this.grids[keyflowId][conclusion.get('consensus_level')]; + _this.addConclusionItem(grid, conclusion); + }, + error: function(arg1, arg2){ + var response = (arg1.status) ? arg1 : arg2; + if (response.responseText) + alert(response.responseText); + else + alert(response.statusText); + } + }) + }) + }) + }, + + /* + * render the view + */ + render: function(){ + var _this = this, + html = document.getElementById(this.template).innerHTML, + template = _.template(html); + this.el.innerHTML = template({ + keyflows: this.keyflows, + consensusLevels: this.consensusLevels + }); + _this.grids = {}; + //this.level1Select = this.el.querySelector('select[name="sort-level-1"]'); + //this.level2Select = this.el.querySelector('select[name="sort-level-2"]'); + //this.level3Select = this.el.querySelector('select[name="sort-level-3"]'); + //function fillSort(select, i){ + //var labels = ['keyflow', 'consensus level', 'section', 'GDSE step']; + //labels.forEach(function(label){ + //var option = document.createElement('option'); + //option.innerHTML = label; + //select.appendChild(option); + //}) + //select.selectedIndex = i; + //} + //fillSort(this.level1Select, 0); + //fillSort(this.level2Select, 1); + //fillSort(this.level3Select, 2); + this.keyflows.forEach(function(keyflow){ + var keyflowPanel = _this.el.querySelector('#keyflow-' + keyflow.id), + conclusions = _this.conclusionsInCasestudy[keyflow.id]; + _this.grids[keyflow.id] = {}; + _this.consensusLevels.forEach(function(level){ + var levelPanel = keyflowPanel.querySelector('.item-panel[data-level="' + level.id + '"]') + _this.grids[keyflow.id][level.id] = new Muuri(levelPanel, { + items: '.conclusion-item', + dragAxis: 'y', + layoutDuration: 400, + layoutEasing: 'ease', + dragEnabled: true, + dragSortInterval: 0, + dragReleaseDuration: 400, + dragReleaseEasing: 'ease' + }); + }) + conclusions.forEach(function(conclusion){ + var level = conclusion.get('consensus_level'), + panel = keyflowPanel.querySelector('.item-panel[data-level="' + level + '"]'), + grid = _this.grids[keyflow.id][level]; + _this.addConclusionItem(grid, conclusion); + }) + }) + }, + + addConclusionItem: function(grid, conclusion){ + var _this = this, + item = document.createElement('div'), + html = document.getElementById('conclusion-item-template').innerHTML, + template = _.template(html); + item.innerHTML = template({ + conclusion: conclusion, + section: this.sections.get(conclusion.get('section')).get('name') + }); + item.classList.add('conclusion-item', 'draggable', 'raised'); + item.style.position = 'absolute'; + item.style.height = '100px'; + grid.add(item); + new Viewer.default(item); + + item.querySelector('button[name="remove"]').addEventListener('click', function(){ + var message = gettext('Do you really want to delete the conclusion?'); + _this.confirm({ message: message, onConfirm: function(){ + conclusion.destroy({ + success: function() { grid.remove(item, { removeElements: true }); }, + error: _this.onError, + wait: true + }) + }}); + }) + } + + }); + return ConclusionsView; +} +); + diff --git a/repair/js/views/conclusions/flow-targets.js b/repair/js/views/conclusions/flow-targets.js index bd1585536..842f43a26 100644 --- a/repair/js/views/conclusions/flow-targets.js +++ b/repair/js/views/conclusions/flow-targets.js @@ -18,7 +18,7 @@ function(_, BaseView, GDSECollection, Muuri){ * @param {HTMLElement} options.el element the view will be rendered in * @param {string} options.template id of the script element containing the underscore template to render this view * @param {module:models/CaseStudy} options.caseStudy the casestudy of the keyflow - * @param {module:models/CaseStudy} options.keyflowId the keyflow the objectives belong to + * @param {module:models/CaseStudy} options.keyflow the keyflow the objectives belong to * * @constructs * @see http://backbonejs.org/#View @@ -30,8 +30,7 @@ function(_, BaseView, GDSECollection, Muuri){ this.caseStudy = options.caseStudy; this.aims = options.aims; this.objectives = options.objectives; - this.keyflowId = options.keyflowId; - this.keyflowName = options.keyflowName; + this.keyflow = options.keyflow; this.users = options.users; this.targetValues = new GDSECollection([], { @@ -95,12 +94,12 @@ function(_, BaseView, GDSECollection, Muuri){ table = this.el.querySelector('table[name="indicator-table"]'), header = table.createTHead().insertRow(0), fTh = document.createElement('th'); - fTh.innerHTML = gettext('Objectives for key flow ' + this.keyflowName + ''); + fTh.innerHTML = gettext('Objectives for key flow ' + this.keyflow.get('name') + ''); header.appendChild(fTh); var indicatorColumns = []; this.indicators.forEach(function(indicator){ - indicatorColumns.push(indicator.id) + indicatorColumns.push(indicator) var th = document.createElement('th'); th.innerHTML = indicator.get('name'); header.appendChild(th); @@ -110,18 +109,21 @@ function(_, BaseView, GDSECollection, Muuri){ this.aims.forEach(function(aim){ var row = table.insertRow(-1), - desc = aim.get('description') || ''; - var panelItem = _this.panelItem(aim.get('text'), { - popoverText: desc.replace(/\n/g, "
") + desc = aim.get('description') || '', + title = aim.get('text'); + var panelItem = _this.panelItem(title, { + popoverText: '' + title + '
' + desc.replace(/\n/g, "
"), }) panelItem.style.maxWidth = '500px'; row.insertCell(0).appendChild(panelItem); var indicatorCount = _this.indicatorCountPerAim[aim.id]; - indicatorColumns.forEach(function(indicatorId){ - var count = indicatorCount[indicatorId], + indicatorColumns.forEach(function(indicator){ + var count = indicatorCount[indicator.id], cell = row.insertCell(-1); if (count){ - var item = _this.panelItem(count + ' x'); + var item = _this.panelItem(count + ' x', { + popoverText: '' + indicator.get('name') + ' ' + gettext('used') + ' ' + count + ' x ' + 'in' + ' ' + aim.get('text') + '' + }); item.style.backgroundImage = 'none'; item.style.width = '50px'; cell.appendChild(item); @@ -141,12 +143,12 @@ function(_, BaseView, GDSECollection, Muuri){ table = this.el.querySelector('#target-values-table'), header = table.createTHead().insertRow(0), fTh = document.createElement('th'); - fTh.innerHTML = gettext('Indicators used as target setting in the key flow ' + this.keyflowName + ''); + fTh.innerHTML = gettext('Indicators used as target setting in the key flow ' + this.keyflow.get('name') + ''); header.appendChild(fTh); var userColumns = []; this.users.forEach(function(user){ - userColumns.push(user.id); + userColumns.push(user); var name = user.get('alias') || user.get('name'), th = document.createElement('th'); th.innerHTML = name; @@ -155,18 +157,24 @@ function(_, BaseView, GDSECollection, Muuri){ this.indicators.forEach(function(indicator){ var row = table.insertRow(-1), text = indicator.get('name') + ' (' + indicator.get('spatial_reference').toLowerCase() + ')'; - var panelItem = _this.panelItem(text) + var panelItem = _this.panelItem(text, { + popoverText: text + }) panelItem.style.maxWidth = '500px'; row.insertCell(0).appendChild(panelItem); var userTargets = _this.userTargetsPerIndicator[indicator.id]; if (!userTargets) return; - userColumns.forEach(function(userId){ - var target = userTargets[userId], - cell = row.insertCell(-1); + userColumns.forEach(function(user){ + var target = userTargets[user.id], + cell = row.insertCell(-1), + name = user.get('alias') || user.get('name'); if (target != null){ var targetValue = _this.targetValues.get(target.get('target_value')), amount = targetValue.get('number'), - item = _this.panelItem(targetValue.get('text')); + targetText = targetValue.get('text'), + item = _this.panelItem(targetText, { + popoverText: '' + name + '
' + text + '
' + targetText + }); cell.appendChild(item); var hue = (amount >= 0) ? 90 : 0, // green or red sat = 100 - 70 * Math.abs(Math.min(amount, 1)), diff --git a/repair/js/views/conclusions/manage-notepad.js b/repair/js/views/conclusions/manage-notepad.js index 07b9e3dce..47c1c0db5 100644 --- a/repair/js/views/conclusions/manage-notepad.js +++ b/repair/js/views/conclusions/manage-notepad.js @@ -5,7 +5,7 @@ define(['underscore','views/common/baseview', 'collections/gdsecollection', function(_, BaseView, GDSECollection, Muuri){ /** * - * @author Christoph Franke, Balázs Dukai + * @author Christoph Franke * @name module:views/SetupNotepadView * @augments Backbone.View */ diff --git a/repair/js/views/conclusions/modified-flows.js b/repair/js/views/conclusions/modified-flows.js index cee7d5343..c918262ac 100644 --- a/repair/js/views/conclusions/modified-flows.js +++ b/repair/js/views/conclusions/modified-flows.js @@ -20,7 +20,7 @@ function(_, BaseView, GDSECollection, BarChart){ * @param {HTMLElement} options.el element the view will be rendered in * @param {string} options.template id of the script element containing the underscore template to render this view * @param {module:models/CaseStudy} options.caseStudy the casestudy of the keyflow - * @param {module:models/CaseStudy} options.keyflowId the keyflow the objectives belong to + * @param {module:models/CaseStudy} options.keyflow the keyflow the objectives belong to * * @constructs * @see http://backbonejs.org/#View @@ -30,8 +30,7 @@ function(_, BaseView, GDSECollection, BarChart){ var _this = this; this.template = options.template; this.caseStudy = options.caseStudy; - this.keyflowId = options.keyflowId; - this.keyflowName = options.keyflowName; + this.keyflow = options.keyflow; this.users = options.users; this.strategies = options.strategies; this.indicators = options.indicators; @@ -73,7 +72,7 @@ function(_, BaseView, GDSECollection, BarChart){ // ToDo: what if no status quo? if (!statusquoValue) return; _this.indicatorValues[indicator.id][user.id] = data; - data.deltaPerc = (isAbs) ? deltaValue / statusquoValue * 100 : deltaValue; + data.deltaPerc = (isAbs) ? deltaValue: deltaValue / statusquoValue * 100; }, error: _this.onError }) @@ -116,7 +115,7 @@ function(_, BaseView, GDSECollection, BarChart){ table = this.el.querySelector('table[name="indicator-table"]'), header = table.createTHead().insertRow(0), fTh = document.createElement('th'); - fTh.innerHTML = gettext('Flow indicators for key flow ' + this.keyflowName + ''); + fTh.innerHTML = gettext('Flow indicators for key flow ' + this.keyflow.get('name') + ''); header.appendChild(fTh); var userColumns = []; @@ -160,7 +159,7 @@ function(_, BaseView, GDSECollection, BarChart){ header = table.createTHead().insertRow(0), fTh = document.createElement('th'), sTh = document.createElement('th'); - fTh.innerHTML = gettext('Flow indicators for key flow ' + this.keyflowName + ''); + fTh.innerHTML = gettext('Flow indicators for key flow ' + this.keyflow.get('name') + ''); sTh.innerHTML = gettext('Status'); header.appendChild(fTh); header.appendChild(sTh); @@ -192,7 +191,6 @@ function(_, BaseView, GDSECollection, BarChart){ targetText = (targetValue) ? targetValue.get('text') : null, deltaPerc = (data) ? data.deltaPerc : 0, dt = (deltaPerc > 0) ? '+' + _this.format(deltaPerc) : _this.format(deltaPerc); - chartData.push({ group: user.get('alias') || user.get('name'), values: [ diff --git a/repair/js/views/conclusions/objectives.js b/repair/js/views/conclusions/objectives.js index c9056af8c..5d86a7f0f 100644 --- a/repair/js/views/conclusions/objectives.js +++ b/repair/js/views/conclusions/objectives.js @@ -19,7 +19,7 @@ function(_, BaseView, GDSECollection, Muuri){ * @param {HTMLElement} options.el element the view will be rendered in * @param {string} options.template id of the script element containing the underscore template to render this view * @param {module:models/CaseStudy} options.caseStudy the casestudy of the keyflow - * @param {module:models/CaseStudy} options.keyflowId the keyflow the objectives belong to + * @param {module:models/CaseStudy} options.keyflow the keyflow the objectives belong to * * @constructs * @see http://backbonejs.org/#View @@ -31,8 +31,7 @@ function(_, BaseView, GDSECollection, Muuri){ this.caseStudy = options.caseStudy; this.aims = options.aims; this.objectives = options.objectives; - this.keyflowId = options.keyflowId; - this.keyflowName = options.keyflowName; + this.keyflow = options.keyflow; this.users = options.users; // ToDo: non-keyflow related collections obviously don't change when changing keyflow @@ -63,7 +62,7 @@ function(_, BaseView, GDSECollection, Muuri){ var _this = this; objectivesTable = this.el.querySelector('#objectives-table'), generalTable = this.el.querySelector('#general-objectives-table'); - var title = gettext('Objectives for keyflow ' + this.keyflowName + ''); + var title = gettext('Objectives for keyflow ' + this.keyflow.get('name') + ''); this.renderObjTable(this.objectives, this.aims, objectivesTable, title); title = gettext('General objectives'); this.renderObjTable(this.generalObjectives, this.generalAims, generalTable, title); @@ -82,7 +81,7 @@ function(_, BaseView, GDSECollection, Muuri){ this.users.forEach(function(user){ var name = user.get('alias') || user.get('name'), th = document.createElement('th'); - userColumns.push(user.id); + userColumns.push(user); th.innerHTML = name; header.appendChild(th); var userObjectives = objectives.filterBy({'user': user.get('user')}); @@ -123,11 +122,14 @@ function(_, BaseView, GDSECollection, Muuri){ item = _this.createAimItem(aim, i); row.insertCell(0).appendChild(item); var aimRank = rankingMap[aim.id]; - userColumns.forEach(function(userId){ + userColumns.forEach(function(user){ var cell = row.insertCell(-1), - rank = aimRank[userId]; + rank = aimRank[user.id], + name = user.get('alias') || user.get('name'); if (rank) { - var item = _this.panelItem('#' + rank); + var item = _this.panelItem('#' + rank, { + popoverText: name + ' ' + gettext('ranked') + ' ' + aim.get('text') + ' #' + rank + }); item.style.width = '50px'; item.style.backgroundImage = 'none'; cell.appendChild(item); @@ -142,10 +144,11 @@ function(_, BaseView, GDSECollection, Muuri){ }, createAimItem: function(aim, rank){ - var desc = aim.get('description') || ''; + var desc = aim.get('description') || '', + title = aim.get('text'); - var panelItem = this.panelItem(aim.get('text'), { - popoverText: desc.replace(/\n/g, "
"), + var panelItem = this.panelItem(title, { + popoverText: '' + title + '
' + desc.replace(/\n/g, "
"), overlayText: '#' + rank }) panelItem.style.maxWidth = '500px'; diff --git a/repair/js/views/conclusions/strategies.js b/repair/js/views/conclusions/strategies.js index 4fa24e78d..f8dc20cbe 100644 --- a/repair/js/views/conclusions/strategies.js +++ b/repair/js/views/conclusions/strategies.js @@ -20,7 +20,7 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ * @param {HTMLElement} options.el element the view will be rendered in * @param {string} options.template id of the script element containing the underscore template to render this view * @param {module:models/CaseStudy} options.caseStudy the casestudy of the keyflow - * @param {module:models/CaseStudy} options.keyflowId the keyflow the objectives belong to + * @param {module:models/CaseStudy} options.keyflow the keyflow the objectives belong to * * @constructs * @see http://backbonejs.org/#View @@ -30,13 +30,12 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ var _this = this; this.template = options.template; this.caseStudy = options.caseStudy; - this.keyflowId = options.keyflowId; - this.keyflowName = options.keyflowName; + this.keyflow = options.keyflow; this.users = options.users; this.solutions = new GDSECollection([], { apiTag: 'solutions', - apiIds: [this.caseStudy.id, this.keyflowId], + apiIds: [this.caseStudy.id, this.keyflow.id], comparator: 'implementation_count' }); this.strategies = options.strategies; @@ -46,12 +45,12 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ }); this.activities = new GDSECollection([], { apiTag: 'activities', - apiIds: [this.caseStudy.id, this.keyflowId], + apiIds: [this.caseStudy.id, this.keyflow.id], comparator: 'name' }); this.activityGroups = new GDSECollection([], { apiTag: 'activitygroups', - apiIds: [this.caseStudy.id, this.keyflowId], + apiIds: [this.caseStudy.id, this.keyflow.id], comparator: 'name' }); @@ -66,7 +65,7 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ this.strategies.forEach(function(strategy){ var implementations = new GDSECollection([], { apiTag: 'solutionsInStrategy', - apiIds: [_this.caseStudy.id, _this.keyflowId, strategy.id] + apiIds: [_this.caseStudy.id, _this.keyflow.id, strategy.id] }); promises.push(implementations.fetch({ success: function (){ @@ -99,8 +98,13 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ _this.solutions.forEach(function(solution){ var questions = new GDSECollection([], { apiTag: 'questions', - apiIds: [_this.caseStudy.id, _this.keyflowId, solution.id] + apiIds: [_this.caseStudy.id, _this.keyflow.id, solution.id] }); + solution.areas = new GDSECollection([], { + apiTag: 'possibleImplementationAreas', + apiIds: [_this.caseStudy.id, _this.keyflow.id, solution.id] + }); + promises.push(solution.areas.fetch()); promises.push(questions.fetch({ success: function (){ _this.questions[solution.id] = questions; @@ -160,6 +164,8 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ // quantity values per user and question this.quantities = {}; + // drawings per user and possible implementation area + this.implementationAreas = {}; var i = 0; this.users.forEach(function(user){ @@ -211,6 +217,15 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ _this.quantities[user.id][quantity.question] = []; _this.quantities[user.id][quantity.question].push(quantity.value); }); + + // memorize drawings inputs by users + implementation.get('areas').forEach(function(area){ + if (!_this.implementationAreas[user.id]) _this.implementationAreas[user.id] = {}; + if (!_this.implementationAreas[user.id][area.possible_implementation_area]) + _this.implementationAreas[user.id][area.possible_implementation_area] = []; + _this.implementationAreas[user.id][area.possible_implementation_area].push(area.geom); + }); + // memorize activities directly affected by user strategies solution.get('affected_activities').forEach(function(activityId){ var users = _this.directlyAffectedActivities[activityId]; @@ -300,18 +315,15 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ // Step 5 drawImplementations: function(){ - var solution = this.solutions.get(this.solutionSelect.value), - possImplArea = solution.get('possible_implementation_area'), + var selectedOption = this.solutionSelect.options[this.solutionSelect.selectedIndex], + possibleAreaId = selectedOption.value, + solution = this.solutions.get(selectedOption.dataset.solution), + possImplArea = solution.areas.get(possibleAreaId).get('geom'), focusarea = this.caseStudy.get('properties').focusarea, _this = this; - if (possImplArea) { - var poly = new ol.geom.MultiPolygon(possImplArea.coordinates); - this.strategiesMap.centerOnPolygon(poly, { projection: this.projection }); - } else if (focusarea){ - var poly = new ol.geom.MultiPolygon(focusarea.coordinates); - this.strategiesMap.centerOnPolygon(poly, { projection: this.projection }); - }; + var poly = new ol.geom.MultiPolygon(possImplArea.coordinates); + this.strategiesMap.centerOnPolygon(poly, { projection: this.projection }); this.users.forEach(function(user){ _this.strategiesMap.clearLayer('user' + user.id); @@ -322,12 +334,12 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ implementations = _this.implementations[strategy.id].where({solution: solution.id}), userName = user.get('alias') || user.get('name'); implementations.forEach(function(solutionImpl){ - var implAreas = solutionImpl.get('geom'); - // implementation areas are collections - if (!implAreas || implAreas.geometries.length == 0) return; - implAreas.geometries.forEach(function(geom){ + var geometries = _this.implementationAreas[user.id][possibleAreaId]; + if (!geometries) return; + geometries.forEach(function(geom){ + if (!geom) return; _this.strategiesMap.addGeometry(geom.coordinates, { - projection: 'EPSG:3857', + projection: 'EPSG:4326', layername: 'user' + user.id, type: geom.type, tooltip: userName @@ -343,7 +355,7 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ table = this.el.querySelector('#solution-question-table'), header = table.createTHead().insertRow(0), fTh = document.createElement('th'); - fTh.innerHTML = gettext('Solutions for key flow ' + this.keyflowName + ''); + fTh.innerHTML = gettext('Solutions for key flow ' + this.keyflow.get('name') + ''); header.appendChild(fTh); var userColumns = []; this.users.forEach(function(user){ @@ -371,7 +383,10 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ var row = table.insertRow(-1), text = solution.get('name'), questions = _this.questions[solution.id]; - var solItem = _this.panelItem(text, { overlayText: '0x' }); + var solItem = _this.panelItem(text, { + overlayText: '0x', + popoverText: text + }); solItem.style.maxWidth = '600px'; row.insertCell(0).appendChild(solItem); _this.users.forEach(function(user){ @@ -394,8 +409,9 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ if (!_this.quantities[user.id]) return; var values = _this.quantities[user.id][question.id], isAbsolute = question.get('is_absolute'); + if (!values) return; values.forEach(function(value){ - var v = (isAbsolute) ? value + ' ' + gettext('t/year') : parseFloat(value) * 100 + '%', + var v = (isAbsolute) ? value + ' ' + gettext('t/year') : parseFloat(value) + '%', t = '' + (user.get('alias') || user.get('name')) + '
' + question.get('question') + ':
' + v, panelItem = _this.panelItem(v, { popoverText: t }); panelItem.style.float = 'left'; @@ -421,8 +437,8 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ ifTh = document.createElement('th'), levelSelect = this.el.querySelector('select[name="level-select"]'), gName = (levelSelect.value == 'activity') ? 'Activities' : 'Activity groups'; - fTh.innerHTML = gName + ' ' + gettext('directly affected by user strategies
in key flow ' + this.keyflowName + ''); - ifTh.innerHTML = gName + ' ' + gettext('indirectly affected by user strategies
in key flow ' + this.keyflowName + ''); + fTh.innerHTML = gName + ' ' + gettext('directly affected by user strategies
in key flow ' + this.keyflow.get('name') + ''); + ifTh.innerHTML = gName + ' ' + gettext('indirectly affected by user strategies
in key flow ' + this.keyflow.get('name') + ''); directHeader.appendChild(fTh); indirectHeader.appendChild(ifTh); var userColumns = []; @@ -439,7 +455,10 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ function addRow(table, node, userSet, totalCount){ var row = table.insertRow(-1), text = node.get('name'); - var panelItem = _this.panelItem(text, { overlayText: totalCount + 'x' }); + var panelItem = _this.panelItem(text, { + overlayText: totalCount + 'x', + popoverText: text + }); panelItem.style.width = '500px'; row.insertCell(0).appendChild(panelItem); _this.users.forEach(function(user){ @@ -540,7 +559,7 @@ function(_, BaseView, GDSECollection, Map, ol, chroma){ stakeholders.forEach(function(stakeholder){ var row = table.insertRow(-1), text = stakeholder.get('name'); - var panelItem = _this.panelItem(text); + var panelItem = _this.panelItem(text, {popoverText: text}); panelItem.style.maxWidth = '500px'; row.insertCell(0).appendChild(panelItem); diff --git a/repair/js/views/data-entry/actors-flows.js b/repair/js/views/data-entry/actors-flows.js index 75479a64a..176ad3eed 100644 --- a/repair/js/views/data-entry/actors-flows.js +++ b/repair/js/views/data-entry/actors-flows.js @@ -191,6 +191,9 @@ var FlowsEditView = BaseView.extend( plugins: ["wholerow", "ui", "types", "themes"] }); $(this.dataTree).on("select_node.jstree", this.nodeSelected); + $(this.dataTree).bind("hover_node.jstree", function (e, data) { + $("#" + data.node.id).prop('title', data.node.text); + }) this.filterSelect = this.el.querySelector('#included-filter-select'); this.actorsTable = $('#actors-table').DataTable(); $('#actors-table tbody').on('click', 'tr', function () { diff --git a/repair/js/views/data-entry/bulk-upload.js b/repair/js/views/data-entry/bulk-upload.js index 3f6f88993..17278d084 100644 --- a/repair/js/views/data-entry/bulk-upload.js +++ b/repair/js/views/data-entry/bulk-upload.js @@ -243,7 +243,7 @@ var BulkUploadView = BaseView.extend( dismissible: true }) alertDiv.querySelector('.close').style.top = '-20px'; - _this.log('

' + msg + '

') + _this.log('

' + msg + '


') _this.loader.deactivate(); _this.refreshStatus(); } diff --git a/repair/js/views/data-entry/edit-actor.js b/repair/js/views/data-entry/edit-actor.js index 423b15f1a..9719c389a 100644 --- a/repair/js/views/data-entry/edit-actor.js +++ b/repair/js/views/data-entry/edit-actor.js @@ -315,6 +315,7 @@ var EditActorView = BaseView.extend( coordDiv.innerHTML = '(' + utils.formatCoords(coords) + ')'; _this.triggerChange(); }, + draggable: true, layername: layername }); }; @@ -642,6 +643,7 @@ var EditActorView = BaseView.extend( _this.tempCoords = coords; elGeom.innerHTML = utils.formatCoords(coords); }, + draggable: true, onRemove: removeMarkerCallback, removable: true, layername: _this.activeType diff --git a/repair/js/views/data-entry/edit-node.js b/repair/js/views/data-entry/edit-node.js index bcaf13b27..24fc1cfb5 100644 --- a/repair/js/views/data-entry/edit-node.js +++ b/repair/js/views/data-entry/edit-node.js @@ -468,9 +468,9 @@ var EditNodeView = BaseView.extend( sourceButton.style.maxWidth = '200px'; var btnClass = (currentId) ? 'btn-primary': 'btn-warning'; sourceButton.classList.add('btn', 'inverted', 'square', btnClass); - if (currentId){ - var publication = this.publications.get(currentId) - var title = publication.get('title'); + if (currentId && this.publications.get(currentId)){ + var publication = this.publications.get(currentId), + title = publication.get('title'); sourceButton.innerHTML = title; sourceButton.setAttribute('data-publication-id', currentId) } diff --git a/repair/js/views/status-quo/objectives.js b/repair/js/views/status-quo/objectives.js index 247ab9d30..c57ddba7c 100644 --- a/repair/js/views/status-quo/objectives.js +++ b/repair/js/views/status-quo/objectives.js @@ -4,7 +4,7 @@ define(['underscore','views/common/baseview', 'collections/gdsecollection', function(_, BaseView, GDSECollection, GDSEModel, Muuri){ /** * - * @author Christoph Franke, Balázs Dukai + * @author Christoph Franke * @name module:views/ChallengesAimsView * @augments Backbone.View */ diff --git a/repair/js/views/conclusions/sustainability.js b/repair/js/views/status-quo/sustainability.js similarity index 81% rename from repair/js/views/conclusions/sustainability.js rename to repair/js/views/status-quo/sustainability.js index 7013efc2a..ce9eb5a75 100644 --- a/repair/js/views/conclusions/sustainability.js +++ b/repair/js/views/status-quo/sustainability.js @@ -28,14 +28,17 @@ var SustainabilityView = BaseView.extend( var _this = this; this.caseStudy = options.caseStudy; + this.scale = 1; + + this.fileAttr = options.fileAttr; + this.keyflow = new GDSEModel({ id: options.keyflowId }, { apiTag: 'keyflowsInCaseStudy', apiIds: [ this.caseStudy.id ] }); - this.mode = options.mode || 0; this.scale = 1; - this.fileAttr = 'sustainability_conclusions'; + this.fileAttr = options.fileAttr; this.keyflow.fetch({ success: this.render, @@ -51,7 +54,8 @@ var SustainabilityView = BaseView.extend( 'click #prev': 'prevPage', 'click #next': 'nextPage', 'click .fullscreen-toggle': 'toggleFullscreen', - 'click #upload-sustainability-file': 'uploadFile' + 'click #upload-sustainability-file': 'uploadFile', + 'click #remove-sustainability-file': 'removeFile' }, /* @@ -60,11 +64,13 @@ var SustainabilityView = BaseView.extend( render: function(){ SustainabilityView.__super__.render.call(this); var _this = this; + var url = this.keyflow.get(this.fileAttr); this.canvas = this.el.querySelector("canvas"); this.canvasWrapper = this.el.querySelector('#canvas-wrapper'); this.pageStatus = this.el.querySelector('#page-status'); this.pdfInput = this.el.querySelector('#sustainability-file-input'); - var url = this.keyflow.get(this.fileAttr); + this.status = document.createElement('h3'); + this.el.appendChild(this.status); if (url) { this.loader.activate(); PDFJS.getDocument({url: url}).then(function(pdf) { @@ -73,12 +79,15 @@ var SustainabilityView = BaseView.extend( _this.renderPage(_this.pageNumber); _this.loader.deactivate(); }); + } else { + this.status.innerHTML = gettext('There is no report not set tup yet.'); } }, showFilePreview: function(event){ var input = event.target, _this = this; + this.status.innerHTML = ''; if (input.files && input.files[0]){ var reader = new FileReader(); reader.onload = function (e) { @@ -155,7 +164,7 @@ var SustainabilityView = BaseView.extend( if (this.pdfInput.files && this.pdfInput.files[0]){ var pdf = this.pdfInput.files[0], data = {}; - data[_this.fileAttr] = pdf; + data[this.fileAttr] = pdf; //this.keyflow.set('sustainability_conclusions', pdf); this.keyflow.save(data, { success: function () { @@ -169,6 +178,29 @@ var SustainabilityView = BaseView.extend( else { this.alert(gettext('No file selected. Canceling upload...')) } + }, + + removeFile: function(){ + if (!this.keyflow.get(this.fileAttr)) { + //this.canvasWrapper.style.display = 'none'; + return; + } + var data = {}, + _this = this; + data[this.fileAttr] = null; + this.confirm({ + message: gettext('Do you want to remove the currently uploaded report from the keyflow?'), + onConfirm: function(){ + _this.keyflow.save(data, { + success: function () { + _this.status.innerHTML = gettext('There is no report not set tup yet.'); + _this.canvasWrapper.style.display = 'none'; + }, + error: _this.onError, + patch: true + }); + } + }) } }); diff --git a/repair/js/views/status-quo/workshop-flow-assessment.js b/repair/js/views/status-quo/workshop-flow-assessment.js index 7ce431c01..bdce00e05 100644 --- a/repair/js/views/status-quo/workshop-flow-assessment.js +++ b/repair/js/views/status-quo/workshop-flow-assessment.js @@ -256,7 +256,7 @@ var FlowAssessmentWorkshopView = BaseView.extend( data.forEach(function(d){ //continue if no data if (d.value == null) return; - var value = Math.round(d.value); + var value = Math.round(d.value * 100) / 100; if (_this.strategy && _this.modDisplaySelect.value == 'delta') value = d.delta values[d.area] = value; @@ -290,7 +290,7 @@ var FlowAssessmentWorkshopView = BaseView.extend( square.style.float = 'left'; square.style.backgroundColor = color; square.style.marginRight = '5px'; - var entryLabel = _this.format(Math.round(entry)) + ' ' + unit; + var entryLabel = _this.format(Math.round(entry * 100) / 100) + ' ' + unit; if (showDelta && entry > 0) entryLabel = '+' + entryLabel; label.innerHTML = entryLabel; @@ -464,10 +464,9 @@ var FlowAssessmentWorkshopView = BaseView.extend( color = (value >= 0) ? '#23FE01': 'red'; } - Math.round(data[0].value) _this.chartData[indicator.id][id] = { name: id, - value: Math.round(value), + value: Math.round(value * 100) / 100, color: color }; }, @@ -508,7 +507,7 @@ var FlowAssessmentWorkshopView = BaseView.extend( method: "POST", data: { geom: JSON.stringify(geom) }, success: function(data){ - value = (data[0]) ? Math.round(data[0].value) : 0; + value = (data[0]) ? Math.round(data[0].value * 100) / 100 : 0; // always prepend focus area _this.chartData[indicatorId][0] = { name: text, diff --git a/repair/js/views/status-quo/workshop-flows.js b/repair/js/views/status-quo/workshop-flows.js index c882333d8..65ac7158c 100644 --- a/repair/js/views/status-quo/workshop-flows.js +++ b/repair/js/views/status-quo/workshop-flows.js @@ -95,6 +95,7 @@ var FlowsWorkshopView = BaseView.extend( materials: this.materials, filter: filter }); + this.flowsView.loader = this.loader; this.draw(); }, diff --git a/repair/js/views/strategy/flow-target-control.js b/repair/js/views/strategy/flow-target-control.js index c4086a1fa..8b81d7713 100644 --- a/repair/js/views/strategy/flow-target-control.js +++ b/repair/js/views/strategy/flow-target-control.js @@ -90,6 +90,7 @@ var FlowTargetControlView = BaseView.extend( * dom events (managed by jquery) */ events: { + 'click #reload-flow-target-control': 'renderTargetControl' }, render: function(){ @@ -104,7 +105,18 @@ var FlowTargetControlView = BaseView.extend( keyflowId: this.keyflowId, strategy: this.strategy }) - var objectivesPanel = this.el.querySelector('#target-control'); + var li = this.el.querySelector('a[href="#modified-indicators"]'); + $(li).on('shown.bs.tab', function () { + var map = _this.assessmentView.map; + if (map) map.map.updateSize(); + }); + this.renderTargetControl(); + }, + + renderTargetControl: function(){ + var objectivesPanel = this.el.querySelector('#target-control'), + _this = this; + objectivesPanel.innerHTML = ''; this.userObjectives.forEach(function(objective){ var panel = _this.renderObjective(objective); objectivesPanel.appendChild(panel); @@ -222,7 +234,9 @@ var FlowTargetControlView = BaseView.extend( strategy: _this.strategy.id }, success: render, - error: _this.onError + error: function(){ + loader.deactivate(); + } }) return row; diff --git a/repair/js/views/strategy/modified-flows.js b/repair/js/views/strategy/modified-flows.js index 4c273ce5a..7c1393625 100644 --- a/repair/js/views/strategy/modified-flows.js +++ b/repair/js/views/strategy/modified-flows.js @@ -118,6 +118,7 @@ var ModifiedFlowsView = BaseView.extend( filter: filter, strategy: this.strategy }); + this.flowsView.loader = this.loader; this.draw(); }, diff --git a/repair/js/views/strategy/setup-area.js b/repair/js/views/strategy/setup-area.js new file mode 100644 index 000000000..534c0abc6 --- /dev/null +++ b/repair/js/views/strategy/setup-area.js @@ -0,0 +1,172 @@ + +define(['views/common/baseview', 'underscore', 'collections/gdsecollection', + 'models/gdsemodel', 'visualizations/map', 'app-config', 'utils/utils', + 'openlayers', 'bootstrap', 'bootstrap-select'], + +function(BaseView, _, GDSECollection, GDSEModel, Map, config, utils, ol){ +/** +* +* @author Christoph Franke +* @name module:views/PossibleImplementationAreaView +* @augments BaseView +*/ +var PossibleImplementationAreaView = BaseView.extend( + /** @lends module:views/PossibleImplementationAreaView.prototype */ + { + + /** + * + * + * @param {Object} options + * @param {HTMLElement} options.el element the view will be rendered in + * @param {string} options.template id of the script element containing the underscore template to render this view + * @param {module:models/CaseStudy} options.caseStudy the casestudy to add solutions to + * + * @constructs + * @see http://backbonejs.org/#View + */ + initialize: function(options){ + PossibleImplementationAreaView.__super__.initialize.apply(this, [options]); + var _this = this; + + this.template = 'area-template'; + this.solutions = options.solutions; + this.caseStudy = options.caseStudy; + + this.render(); + }, + + /* + * dom events (managed by jquery) + */ + events: { + 'click button[name="show-area"]': 'showArea', + 'click button[name="set-focus-area"]': 'setFocusArea', + 'click button[name="set-casestudy"]': 'setCaseStudy' + }, + + /* + * render the view + */ + render: function(){ + var _this = this, + html = document.getElementById(this.template).innerHTML, + template = _.template(html); + this.el.innerHTML = template({}); + + this.implAreaText = this.el.querySelector('textarea[name="implementation-area"]'); + this.questionInput = this.el.querySelector('input[name="question"]'); + var mapDiv = this.el.querySelector('div[name="area-map"]'); + this.areaMap = new Map({ + el: mapDiv + }); + this.areaMap.addLayer('implementation-area', { + stroke: 'rgb(0, 98, 255)', + fill: 'rgba(0, 98, 255, 0.1)', + strokeWidth: 1, + zIndex: 0 + }); + this.setInputs(); + }, + + setInputs: function(){ + var implArea = this.model.get('geom') || ''; + if(implArea) implArea = JSON.stringify(implArea); + this.implAreaText.value = implArea; + this.questionInput.value = this.model.get('question') || ''; + this.showArea(); + }, + + applyInputs: function(){ + var geoJSON = this.checkGeoJSON(this.implAreaText.value); + this.model.set('geom', geoJSON); + this.model.set('question', this.questionInput.value); + }, + + showArea: function(){ + var implArea = this.implAreaText.value; + if (!implArea) return; + + var geoJSON = this.checkGeoJSON(implArea); + if (!geoJSON) return; + + this.areaMap.clearLayer('implementation-area'); + try { + var poly = this.areaMap.addPolygon(geoJSON.coordinates, { + projection: this.projection, + layername: 'implementation-area', + tooltip: gettext('possible implementation area'), + type: geoJSON.type.toLowerCase() + }); + } + catch(err) { + this.alert(err); + return; + } + this.areaMap.centerOnPolygon(poly, { projection: this.projection }); + }, + + convertFeatureCollection: function(json){ + var geoJSON = new ol.format.GeoJSON(), + features = geoJSON.readFeatures(json), + multipoly = new ol.geom.MultiPolygon(); + features.forEach(function(feature){ + var geom = feature.getGeometry() + if (geom.getType() == 'MultiPolygon'){ + geom.getPolygons().forEach(function(poly){ + multipoly.appendPolygon(poly); + }) + } else + multipoly.appendPolygon(geom); + }) + var geoJSONText = geoJSON.writeGeometry(multipoly); + return JSON.parse(geoJSONText); + }, + + checkGeoJSON: function(geoJSONTxt){ + try { + var geoJSON = JSON.parse(geoJSONTxt); + } + catch(err) { + this.alert(err); + return; + } + + if (geoJSON.type.toLowerCase() == 'featurecollection'){ + geoJSON = this.convertFeatureCollection(geoJSON); + console.log(geoJSON) + } + //if (!geoJSON.coordinates && !geoJSON.type) { + //this.alert(gettext('GeoJSON needs attributes "type" and "coordinates"')); + //} + else if (!['multipolygon', 'polygon'].includes(geoJSON.type.toLowerCase())){ + this.alert(gettext('type has to be MultiPolygon or Polygon')); + return; + } + return geoJSON; + }, + + setFocusArea: function(){ + var focusArea = this.caseStudy.get('properties').focusarea; + if (!focusArea) { + this.onError(gettext('focus area is not set for this case study')); + return; + } + this.implAreaText.value = JSON.stringify(focusArea); + this.showArea(); + }, + + setCaseStudy: function(){ + var geom = this.caseStudy.get('geometry'); + if (!geom) { + this.alert(gettext('case study region is not set for this case study')); + return; + } + this.implAreaText.value = JSON.stringify(geom); + this.showArea(); + }, + +}); +return PossibleImplementationAreaView; +} +); diff --git a/repair/js/views/strategy/setup-question.js b/repair/js/views/strategy/setup-question.js index 05a788b74..d9083c014 100644 --- a/repair/js/views/strategy/setup-question.js +++ b/repair/js/views/strategy/setup-question.js @@ -29,7 +29,7 @@ var QuestionView = BaseView.extend( QuestionView.__super__.initialize.apply(this, [options]); var _this = this; - this.template = options.template; + this.template = 'question-template'; this.solutions = options.solutions; this.render(); diff --git a/repair/js/views/strategy/setup-solution-part.js b/repair/js/views/strategy/setup-solution-part.js index cafffe7ca..3d3d40973 100644 --- a/repair/js/views/strategy/setup-solution-part.js +++ b/repair/js/views/strategy/setup-solution-part.js @@ -1,9 +1,10 @@ define(['views/common/baseview', 'underscore', 'collections/gdsecollection', - 'models/gdsemodel', 'app-config', 'utils/utils', 'bootstrap', - 'bootstrap-select'], + 'models/gdsemodel', 'app-config', 'viewerjs', 'views/common/flowsankey', + 'utils/utils', 'backbone', 'bootstrap', 'bootstrap-select', + 'viewerjs/dist/viewer.css'], -function(BaseView, _, GDSECollection, GDSEModel, config, utils){ +function(BaseView, _, GDSECollection, GDSEModel, config, Viewer, FlowSankeyView, utils, Backbone){ /** * * @author Christoph Franke @@ -28,14 +29,19 @@ var SolutionPartView = BaseView.extend( initialize: function(options){ SolutionPartView.__super__.initialize.apply(this, [options]); var _this = this; - _.bindAll(this, 'toggleNewFlow'); _.bindAll(this, 'toggleHasQuestion'); _.bindAll(this, 'toggleAbsolute'); - _.bindAll(this, 'toggleReferencePart'); _.bindAll(this, 'addAffectedFlow'); - _.bindAll(this, 'toggleNewMaterial'); + _.bindAll(this, 'drawSankey'); + _.bindAll(this, 'linkSelected'); + _.bindAll(this, 'linkDeselected'); + _.bindAll(this, 'checkFlowCount'); + _.bindAll(this, 'checkNodeCount'); - this.template = options.template; + this.template = 'solution-part-template'; + + this.caseStudy = options.caseStudy; + this.keyflowId = options.keyflowId; this.solutions = options.solutions; this.solutionParts = options.solutionParts; @@ -44,6 +50,9 @@ var SolutionPartView = BaseView.extend( this.activityGroups = options.activityGroups; this.activities = options.activities; this.questions = options.questions; + this.areas = options.areas; + this.processes = options.processes; + this.scheme = options.scheme || this.model.get('scheme'); this.render(); }, @@ -51,6 +60,19 @@ var SolutionPartView = BaseView.extend( * dom events (managed by jquery) */ events: { + 'click button[name="check-flow-count"]': 'checkFlowCount' + }, + + /* + * scheme tags as keys and lists of [title, template, preview-image, example-image] as values + */ + schemes: { + 'modification': [gettext('Modify Flow'), 'modify-flow-template', 'schemes/modification.png', 'schemes/modification-example.png'], + 'new': [gettext('New Flow'), 'new-flow-template', 'schemes/new.png', 'schemes/new-example.png'], + 'shiftorigin': [gettext('Shift Origin'), 'shift-origin-template', 'schemes/shift-origin.png', 'schemes/shift-origin-example.png'], + 'shiftdestination': [gettext('Shift Destination'), 'shift-destination-template', 'schemes/shift-destination.png', 'schemes/shift-destination-example.png'], + 'prepend': [gettext('Prepend Flow'), 'prepend-flow-template', 'schemes/prepend.png', 'schemes/prepend-example.png'], + 'append': [gettext('Append Flow'), 'append-flow-template', 'schemes/append.png', 'schemes/append-example.png'] }, /* @@ -60,66 +82,134 @@ var SolutionPartView = BaseView.extend( var _this = this, html = document.getElementById(this.template).innerHTML, template = _.template(html); - this.el.innerHTML = template({}); - this.nameInput = this.el.querySelector('input[name="name"]'); - this.implNewFlowSelect = this.el.querySelector('select[name="impl-new-flow"]'); - this.referencesPartSelect = this.el.querySelector('select[name="references-part"]'); - this.solutionPartSelect = this.el.querySelector('select[name="referenced-part"]'); - this.materialChangeSelect = this.el.querySelector('select[name="material-changes"]'); + var scheme = this.scheme.toLowerCase(), + schemeAttr = this.schemes[scheme], + title = schemeAttr[0], + tmpl = schemeAttr[1], + schemepreview = schemeAttr[2], + schemeexample = schemeAttr[3]; + + this.el.innerHTML = template({ + schemepreview: schemepreview, + schemeexample: schemeexample, + title: title + }); + + html = document.getElementById(tmpl).innerHTML; + template = _.template(html); + this.el.querySelector('#definition-content').innerHTML = template({}); + + this.sankeyWrapper = this.el.querySelector('.sankey-wrapper'); + this.sankeyWrapper.addEventListener('linkSelected', this.linkSelected); + this.sankeyWrapper.addEventListener('linkDeselected', this.linkDeselected); + //this.sankeyWrapper.addEventListener('allDeselected', this.deselectAll); + + this.viewer = new Viewer.default(this.el.querySelector('#scheme-preview')); + new Viewer.default(this.el.querySelector('#affected-flows-tab')); + + this.nameInput = this.el.querySelector('input[name="part-name"]'); + + this.referenceOriginSelect = this.el.querySelector('select[name="reference-origin"]'); + this.referenceDestinationSelect = this.el.querySelector('select[name="reference-destination"]'); + this.referenceMaterialSelect = this.el.querySelector('div[name="reference-material"]'); + this.referenceIncludeChildrenSelect = this.el.querySelector('input[name="include-child-materials"]') + this.referenceProcessSelect = this.el.querySelector('select[name="reference-process"]'); + this.referenceOriginAreaSelect = this.el.querySelector('select[name="reference-origin-area"]'); + this.referenceDestinationAreaSelect = this.el.querySelector('select[name="reference-destination-area"]'); + + this.newOriginSelect = this.el.querySelector('select[name="new-origin"]'); + this.newDestinationSelect = this.el.querySelector('select[name="new-destination"]'); this.newMaterialSelect = this.el.querySelector('div[name="new-material"]'); - this.materialSelect = this.el.querySelector('div[name="material"]'); - this.originSelect = this.el.querySelector('select[name="origin"]'); - this.destinationSelect = this.el.querySelector('select[name="destination"]'); - this.spatialOriginCheck = this.el.querySelector('input[name="origin-in-area"]'); - this.spatialDestinationCheck = this.el.querySelector('input[name="destination-in-area"]'); + this.newProcessSelect = this.el.querySelector('select[name="new-process"]'); + this.newOriginAreaSelect = this.el.querySelector('select[name="new-origin-area"]'); + this.newDestinationAreaSelect = this.el.querySelector('select[name="new-destination-area"]'); + this.newWasteSelect = this.el.querySelector('select[name="new-waste"]'); + this.newHazardousSelect = this.el.querySelector('select[name="new-hazardous"]'); + + this.affectedMaterialSelect = this.el.querySelector('div[name="affected-material"]'); + this.aInput = this.el.querySelector('input[name="a"]'); this.bInput = this.el.querySelector('input[name="b"]'); this.questionSelect = this.el.querySelector('select[name="question"]'); - this.hasQuestionSelect = this.el.querySelector('select[name="has-question"]'); - this.isAbsoluteSelect = this.el.querySelector('select[name="is-absolute"]'); - - this.newTargetSelect = this.el.querySelector('select[name="new-target"]'); - this.keepOriginInput = this.el.querySelector('select[name="keep-origin"]'); - this.mapRequestArea = this.el.querySelector('textarea[name="map-request"]'); - - $(this.originSelect).selectpicker({size: 10, liveSearch: true, width: 'fit'}); - $(this.destinationSelect).selectpicker({size: 10, liveSearch: true, width: 'fit'}); - $(this.newTargetSelect).selectpicker({size: 10, liveSearch: true}); - this.populateActivitySelect(this.originSelect); - // ToDo: null allowed for stocks? - this.populateActivitySelect(this.destinationSelect); - this.populateActivitySelect(this.newTargetSelect); - this.populateSolutionPartSelect(); + this.hasQuestionRadios = this.el.querySelectorAll('input[name="has-question"]'); + this.isAbsoluteRadios = this.el.querySelectorAll('input[name="is-absolute"]'); + + $(this.referenceOriginSelect).selectpicker({size: 8, liveSearch: true, width: 'fit'}); + $(this.referenceDestinationSelect).selectpicker({size: 8, liveSearch: true, width: 'fit'}); + $(this.newOriginSelect).selectpicker({size: 8, liveSearch: true, width: 'fit'}); + $(this.newDestinationSelect).selectpicker({size: 8, liveSearch: true, width: 'fit'}); + + this.populateActivitySelect(this.referenceOriginSelect); + this.populateActivitySelect(this.referenceDestinationSelect); + this.populateActivitySelect(this.newOriginSelect); + this.populateActivitySelect(this.newDestinationSelect); + + this.populateAreaSelect(this.referenceOriginAreaSelect); + this.populateAreaSelect(this.referenceDestinationAreaSelect); + this.populateAreaSelect(this.newOriginAreaSelect); + this.populateAreaSelect(this.newDestinationAreaSelect); + + var defaultText = (scheme == 'new') ? gettext('no specific process') : gettext('No change'); + + this.populateProcessSelect(this.referenceProcessSelect, {defaultOption: gettext('no specific process')}); + this.populateProcessSelect(this.newProcessSelect, {defaultOption: defaultText}); + this.populateQuestionSelect(); this.affectedDiv = this.el.querySelector('#affected-flows'); - this.renderMatFilter(this.materialSelect); - this.renderMatFilter(this.newMaterialSelect); - - this.implNewFlowSelect.addEventListener('change', this.toggleNewFlow); - this.referencesPartSelect.addEventListener('change', this.toggleReferencePart); - this.hasQuestionSelect.addEventListener('change', function(){ - _this.toggleHasQuestion(); - _this.toggleAbsolute(); - }); - this.keepOriginInput.addEventListener('change', function(){ - var label = (this.value == "true") ? gettext('destination'): gettext('origin'); - _this.el.querySelector('div[name="origdestlabel"]').innerHTML = label; - }) - this.isAbsoluteSelect.addEventListener('change', this.toggleAbsolute); - this.questionSelect.addEventListener('change', this.toggleAbsolute); + this.renderMatFilter(this.referenceMaterialSelect, {showCount: true, countChildren: true}); + this.renderMatFilter(this.newMaterialSelect, {defaultOption: defaultText}); + this.renderMatFilter(this.affectedMaterialSelect, {onSelect: this.drawSankey, showCount: true, countChildren: true}); - this.setInputs(this.model); + this.setInputs(); - // at least one checkbox has to be checked - this.spatialOriginCheck.addEventListener('change', function(){ - if (!this.checked) _this.spatialDestinationCheck.checked = true; + if (this.hasQuestion == null) this.hasQuestion = true; + this.hasQuestionRadios.forEach(function(radio){ + radio.addEventListener('change', function(){ + _this.hasQuestion = this.value == 'true'; + _this.toggleHasQuestion(); + _this.toggleAbsolute(); + }); }) - this.spatialDestinationCheck.addEventListener('change', function(){ - if (!this.checked) _this.spatialOriginCheck.checked = true; + + if (this.isAbsolute == null) this.isAbsolute = false; + this.isAbsoluteRadios.forEach(function(radio){ + radio.addEventListener('change', function(){ + _this.isAbsolute = this.value == 'true'; + _this.toggleAbsolute(); + }); }) - this.materialChangeSelect.addEventListener('change', this.toggleNewMaterial); + this.toggleHasQuestion(); + this.toggleAbsolute(); + + // the first table is + var tables = this.el.querySelectorAll('table.part-table'), + changeTable = (scheme != 'new') ? tables[1] : tables[0]; + + if (scheme != 'new') { + refTable = tables[0]; + this.addCount(gettext('Actor count'), refTable, function(label){ + _this.checkNodeCount(label, _this.referenceOriginSelect, _this.referenceOriginAreaSelect); + }, 1) + this.addCount(gettext('Actor count'), refTable, function(label){ + _this.checkNodeCount(label, _this.referenceDestinationSelect, _this.referenceDestinationAreaSelect); + }, 3) + this.addCount(gettext('Flow count'), refTable, function(label){ + _this.checkFlowCount(label); + }) + } + + if (_this.newOriginSelect) + this.addCount(gettext('Actor count'), changeTable, function(label){ + _this.checkNodeCount(label, _this.newOriginSelect, _this.newOriginAreaSelect); + }, 1) + if (_this.newDestinationSelect) + this.addCount(gettext('Actor count'), changeTable, function(label){ + _this.checkNodeCount(label, _this.newDestinationSelect, _this.newDestinationAreaSelect); + }, (scheme != 'new') ? 1 : 3) + + //this.materialChangeSelect.addEventListener('change', this.toggleNewMaterial); // forbid html escape codes in name this.nameInput.addEventListener('keyup', function(){ @@ -129,128 +219,215 @@ var SolutionPartView = BaseView.extend( this.el.querySelector('#affected-flows-tab button.add').addEventListener('click', this.addAffectedFlow); }, - toggleNewMaterial: function(){ - var materialChanges = this.materialChangeSelect.value == 'true'; - this.newMaterialSelect.style.display = (materialChanges) ? 'inline-block' : 'none'; - }, + addCount: function(title, table, onClick, position){ + var row = table.insertRow((position != null) ? position : -1), + cell1 = row.insertCell(0), + cell2 = row.insertCell(1), + _this = this; - toggleNewFlow: function(){ - var implementsNewFlow = this.implNewFlowSelect.value == "true", - modFlowElements = this.el.querySelectorAll('.modified-flow'), - newFlowElements = this.el.querySelectorAll('.new-flow'); - modFlowElements.forEach(function(el){ - el.style.display = (implementsNewFlow) ? 'none' :'inline-block'; - }) - newFlowElements.forEach(function(el){ - el.style.display = (implementsNewFlow) ? 'inline-block' :'none'; + var label = document.createElement('div'); + label.innerHTML = title; + cell1.appendChild(label); + + var countLabel = document.createElement('label'), + countButton = document.createElement('button'); + + countLabel.classList.add('count-label'); + countLabel.innerHTML = '?'; + countButton.classList.add('btn', 'btn-secondary', 'square'); + countButton.innerHTML = gettext('Check'); + + countButton.addEventListener('click', function(){ + onClick(countLabel); }) - this.toggleReferencePart(); + + cell2.appendChild(countLabel); + cell2.appendChild(countButton); + }, + + checkNodeCount: function(elFlowCount, input, areainput){ + if (!elFlowCount || !input) return; + + var data = {}; + + elFlowCount.innerHTML = '?'; + var url = config.api['actors'].format(this.caseStudy.id, this.keyflowId) + 'count/'; + + if (areainput && areainput.value != -1){ + var area = this.areas.get(areainput.value), + geom = area.get('geom'); + data['area'] = JSON.stringify(geom); + } + + Backbone.ajax({ + url: url + '?GET=true&activity=' + input.value, + method: 'POST', + data: data, + success: function(res){ + elFlowCount.innerHTML = res.count; + }, + error: this.onError + }); + + }, + + checkFlowCount: function(elFlowCount){ + var origin_activity = (this.referenceOriginSelect) ? this.referenceOriginSelect.value: null, + destination_activity = (this.referenceDestinationSelect) ? this.referenceDestinationSelect.value: null, + material = (this.referenceMaterialSelect) ? this.referenceMaterialSelect.dataset.selected: null, + includeChildren = this.referenceIncludeChildrenSelect.checked; + + if (material == 'null') material = null; + + if (!elFlowCount) return; + + var queryData = { + 'origin__activity': origin_activity, + 'destination__activity': destination_activity + } + + if (origin_activity == null || destination_activity == null){ + elFlowCount.innerHTML = gettext('Origin and Destination have to be set.') + return; + } + if (material != null) queryData['material'] = material; + + if (includeChildren) queryData['include_child_materials'] = true; + + elFlowCount.innerHTML = '?'; + var process = (this.referenceProcessSelect) ? this.referenceProcessSelect.value: null; + process = (process === '-1') ? null: process; + + var data = {}; + var area = (this.referenceOriginAreaSelect) ? this.referenceOriginAreaSelect.value: null; + origin_area = (area === '-1') ? null: area; + + if (origin_area){ + var area = this.areas.get(origin_area), + geom = area.get('geom'); + data['origin_area'] = JSON.stringify(geom); + } + + area = (this.referenceDestinationAreaSelect) ? this.referenceDestinationAreaSelect.value: null; + destination_area = (area === '-1') ? null: area; + + if (destination_area){ + var area = this.areas.get(destination_area), + geom = area.get('geom'); + data['destination_area'] = JSON.stringify(geom); + } + + var url = config.api['flows'].format(this.caseStudy.id, this.keyflowId) + 'count/'; + + if (process != null) queryData['process'] = process; + + Backbone.ajax({ + url: url + '?' + $.param(queryData), + method: 'POST', + data: data, + success: function(res){ + elFlowCount.innerHTML = res.count; + }, + error: this.onError + }); }, toggleHasQuestion: function(){ - var hasQuestion = this.hasQuestionSelect.value == "true" - questElements = this.el.querySelectorAll('.with-question'), - noQuestElements = this.el.querySelectorAll('.no-question'); + var questElements = this.el.querySelectorAll('.with-question'), + noQuestElements = this.el.querySelectorAll('.no-question'), + _this = this; - if (!hasQuestion) + if (!this.hasQuestion) this.aInput.value = 0; noQuestElements.forEach(function(el){ - el.style.display = (hasQuestion) ? 'none' :'inline-block'; + el.style.display = (_this.hasQuestion) ? 'none' :'inline-block'; }) questElements.forEach(function(el){ - el.style.display = (hasQuestion) ? 'inline-block' :'none'; + el.style.display = (_this.hasQuestion) ? 'inline-block' :'none'; }) }, toggleAbsolute: function(){ var absElements = this.el.querySelectorAll('.is-absolute'), - relElements = this.el.querySelectorAll('.is-relative'); - - var isAbsolute = false; - if (this.hasQuestionSelect.value == "false"){ - isAbsolute = this.isAbsoluteSelect.value == "true"; - } else { - var question = this.questions.get(this.questionSelect.value); - if (question) - isAbsolute = question.get('is_absolute') === true; - } + relElements = this.el.querySelectorAll('.is-relative'), + _this = this; relElements.forEach(function(el){ - el.style.display = (isAbsolute) ? 'none' :'inline-block'; + el.style.display = (_this.isAbsolute) ? 'none' :'inline-block'; }) absElements.forEach(function(el){ - el.style.display = (isAbsolute) ? 'inline-block' :'none'; + el.style.display = (_this.isAbsolute) ? 'inline-block' :'none'; }) - }, - toggleReferencePart: function(){ - var referencePart = this.implNewFlowSelect.value == 'true' && this.referencesPartSelect.value == 'true', - refElements = this.el.querySelectorAll('.reference-part'), - flowElements = this.el.querySelectorAll('.reference-flow'); - flowElements.forEach(function(el){ - el.style.display = (referencePart) ? 'none' :'inline-block'; - }) - refElements.forEach(function(el){ - el.style.display = (referencePart) ? 'inline-block' :'none'; - }) + _this.populateQuestionSelect(); }, setInputs: function(){ + + // hierarchy-select plugin offers no functions to set (actually no functions at all) -> emulate clicking on row + function setMaterial(matSelect, material){ + var li = matSelect.querySelector('li[data-value="' + material + '"]'); + if(li){ + var matItem = li.querySelector('a'); + matItem.click(); + } + } + var _this = this; this.nameInput.value = this.model.get('name') || ''; - this.implNewFlowSelect.value = this.model.get('implements_new_flow') || false; - this.referencesPartSelect.value = this.model.get('references_part') || false; - this.solutionPartSelect.value = this.model.get('implementation_flow_solution_part') || null; - this.originSelect.value = this.model.get('implementation_flow_origin_activity') || null; - this.destinationSelect.value = this.model.get('implementation_flow_destination_activity') || null; - var spatial = this.model.get('implementation_flow_spatial_application') || 'both'; - spatial = spatial.toLowerCase(); - this.spatialOriginCheck.checked = (spatial == 'origin' || spatial == 'both'); - this.spatialDestinationCheck.checked = (spatial == 'destination' || spatial == 'both'); - - this.newTargetSelect.value = this.model.get('new_target_activity') || null; - this.mapRequestArea.value = this.model.get('map_request') || ''; - var keepOrigin = this.model.get('keep_origin') || false; - this.keepOriginInput.value = keepOrigin; - var label = (keepOrigin) ? gettext('destination'): gettext('origin'); - _this.el.querySelector('div[name="origdestlabel"]').innerHTML = label; + //this.scheme = this.model.get('scheme'); + + var refFlow = this.model.get('flow_reference'), + changeFlow = this.model.get('flow_changes'); + + if (refFlow){ + if (this.referenceOriginSelect) this.referenceOriginSelect.value = refFlow.origin_activity; + if (this.referenceDestinationSelect) this.referenceDestinationSelect.value = refFlow.destination_activity; + if (this.referenceMaterialSelect) this.referenceMaterialSelect.select(refFlow.material); + if (this.referenceProcessSelect) this.referenceProcessSelect.value = refFlow.process || -1; + if (this.referenceOriginAreaSelect) this.referenceOriginAreaSelect.value = refFlow.origin_area || -1; + if (this.referenceDestinationAreaSelect) this.referenceDestinationAreaSelect.value = refFlow.destination_area || -1; + if (this.referenceIncludeChildrenSelect) this.referenceIncludeChildrenSelect.checked = refFlow.include_child_materials || false; + } + + if (changeFlow){ + if (this.newOriginSelect) this.newOriginSelect.value = changeFlow.origin_activity; + if (this.newDestinationSelect) this.newDestinationSelect.value = changeFlow.destination_activity; + if (this.newMaterialSelect) this.newMaterialSelect.select(changeFlow.material); + if (this.newProcessSelect) this.newProcessSelect.value = changeFlow.process || -1; + if (this.newOriginAreaSelect) this.newOriginAreaSelect.value = changeFlow.origin_area || -1; + if (this.newDestinationAreaSelect) this.newDestinationAreaSelect.value = changeFlow.destination_area || -1; + if (this.newHazardousSelect) this.newHazardousSelect.value = (changeFlow.hazardous != null) ? changeFlow.waste: -1; + if (this.newWasteSelect) this.newWasteSelect.value = (changeFlow.waste != null) ? changeFlow.waste: -1; + } //this.spatialSelect.value = spatial.toLowerCase() - this.aInput.value = this.model.get('a') || 0; + this.aInput.value = this.model.get('a') || 1; this.bInput.value = this.model.get('b') || 0; var question = this.model.get('question'); this.questionSelect.value = question || -1; - this.hasQuestionSelect.value = (question != null); - this.isAbsoluteSelect.value = this.model.get('is_absolute'); - - // hierarchy-select plugin offers no functions to set (actually no functions at all) -> emulate clicking on row - var material = this.model.get('implementation_flow_material'), - li = this.materialSelect.querySelector('li[data-value="' + material + '"]'); - if(li){ - var matItem = li.querySelector('a'); - matItem.click(); - } - var material = this.model.get('new_material'); - if (material) { - this.materialChangeSelect.value = true; - var li = this.newMaterialSelect.querySelector('li[data-value="' + material + '"]'); - if(li){ - var matItem = li.querySelector('a'); - matItem.click(); - } - } else { - this.materialChangeSelect.value = false; - } - $(this.originSelect).selectpicker('refresh'); - $(this.destinationSelect).selectpicker('refresh'); - $(this.newTargetSelect).selectpicker('refresh'); - this.toggleNewFlow(); + this.hasQuestion = (question != null); + question = this.questions.get(question); + this.isAbsolute = (this.hasQuestion) ? question.get('is_absolute'): this.model.get('is_absolute'); + if (this.scheme.toLowerCase() == 'new') this.isAbsolute = true; + + var questValue = (this.hasQuestion) ? 'true': 'false'; + this.hasQuestionRadios.forEach(function(radio){ + radio.checked = radio.value === questValue; + }) + var absValue = (this.isAbsolute) ? 'true': 'false'; + this.isAbsoluteRadios.forEach(function(radio){ + radio.checked = radio.value === absValue; + }) + + $(this.referenceOriginSelect).selectpicker('refresh'); + $(this.referenceDestinationSelect).selectpicker('refresh'); + $(this.newOriginSelect).selectpicker('refresh'); + $(this.newDestinationSelect).selectpicker('refresh'); this.toggleHasQuestion(); this.toggleAbsolute(); - this.toggleNewMaterial(); - this.toggleReferencePart(); var affected = this.model.get('affected_flows') || []; affected.forEach(function(flow){ @@ -260,32 +437,52 @@ var SolutionPartView = BaseView.extend( applyInputs: function(){ var _this = this; + + var refFlow = {}, changeFlow = {}; + + this.model.set('scheme', this.scheme); this.model.set('name', this.nameInput.value); - this.model.set('implements_new_flow', this.implNewFlowSelect.value); - this.model.set('references_part', this.referencesPartSelect.value); - this.model.set('implementation_flow_solution_part', (this.solutionPartSelect.value != "-1") ? this.solutionPartSelect.value: null); - this.model.set('implementation_flow_origin_activity', (this.originSelect.value != "-1") ? this.originSelect.value: null); - this.model.set('implementation_flow_destination_activity', (this.destinationSelect.value != "-1") ? this.destinationSelect.value: null); - var selectedMaterial = this.materialSelect.dataset.selected; - this.model.set('implementation_flow_material', selectedMaterial); - var newMaterial = (this.materialChangeSelect.value == 'true') ? this.newMaterialSelect.dataset.selected: null; - this.model.set('new_material', newMaterial); - var spatial = (this.spatialOriginCheck.checked && this.spatialDestinationCheck.checked) ? 'both': - (this.spatialOriginCheck.checked) ? 'origin': 'destination'; - this.model.set('implementation_flow_spatial_application', spatial); + + refFlow.origin_activity = (this.referenceOriginSelect) ? this.referenceOriginSelect.value: null; + refFlow.destination_activity = (this.referenceDestinationSelect) ? this.referenceDestinationSelect.value : null; + var material = (this.referenceMaterialSelect) ? parseInt(this.referenceMaterialSelect.dataset.selected): null; + if (material == 'null') material = null; + refFlow.material = material; + if (this.referenceIncludeChildrenSelect) + refFlow.include_child_materials = this.referenceIncludeChildrenSelect.checked; + + var process = (this.referenceProcessSelect) ? this.referenceProcessSelect.value: null; + refFlow.process = (process === '-1') ? null: process; + var area = (this.referenceOriginAreaSelect) ? this.referenceOriginAreaSelect.value: null; + refFlow.origin_area = (area === '-1') ? null: area; + area = (this.referenceDestinationAreaSelect) ? this.referenceDestinationAreaSelect.value: null; + refFlow.destination_area = (area === '-1') ? null: area; + + this.model.set('flow_reference', refFlow); + + changeFlow.origin_activity = (this.newOriginSelect) ? this.newOriginSelect.value: null; + changeFlow.destination_activity = (this.newDestinationSelect) ? this.newDestinationSelect.value : null; + material = (this.newMaterialSelect) ? parseInt(this.newMaterialSelect.dataset.selected): null; + if (material == 'null') material = null; + changeFlow.material = material; + process = (this.newProcessSelect) ? this.newProcessSelect.value: null; + changeFlow.process = (process === '-1') ? null: process; + area = (this.newOriginAreaSelect) ? this.newOriginAreaSelect.value: null; + changeFlow.origin_area = (area === '-1') ? null: area; + area = (this.newDestinationAreaSelect) ? this.newDestinationAreaSelect.value: null; + changeFlow.destination_area = (area === '-1') ? null: area; + process = (this.newProcessSelect) ? this.newProcessSelect.value: null; + changeFlow.waste = (this.newWasteSelect) ? this.newWasteSelect.value: -1; + changeFlow.hazardous = (this.newHazardousSelect) ? this.newHazardousSelect.value: -1; + + this.model.set('flow_changes', changeFlow); + this.model.set('documentation', ''); - this.model.set('map_request', ''); this.model.set('a', this.aInput.value); this.model.set('b', this.bInput.value); var question = this.questionSelect.value; - var hasQuestion = this.hasQuestionSelect.value == "true"; - this.model.set('question', (hasQuestion && question != "-1") ? question: null); - - this.model.get('is_absolute', this.isAbsoluteSelect.value); - - this.model.set('new_target_activity', (this.newTargetSelect.value != "-1") ? this.newTargetSelect.value: null); - this.model.set('keep_origin', this.keepOriginInput.value); - this.model.set('map_request', this.mapRequestArea.value); + this.model.set('question', (this.hasQuestion && question != "-1") ? question: null); + this.model.set('is_absolute', this.isAbsolute); var affectedFlowRows = this.affectedDiv.querySelectorAll('.row'), affectedFlows = []; @@ -307,27 +504,18 @@ var SolutionPartView = BaseView.extend( this.model.set('affected_flows', affectedFlows); }, - populateSolutionPartSelect: function(){ - var _this = this, - newFlowParts = this.solutionParts.filterBy({ implements_new_flow: true }); - newFlowParts.forEach(function(part){ - var option = document.createElement('option'); - option.value = part.id; - option.innerHTML = part.get('name'); - _this.solutionPartSelect.appendChild(option); - }) - }, - populateQuestionSelect: function(){ var _this = this; utils.clearSelect(this.questionSelect); + var questions = this.questions.where({is_absolute: this.isAbsolute}); + var option = document.createElement('option'); option.value = -1; option.text = gettext('Select'); option.disabled = true; this.questionSelect.appendChild(option); - this.questions.forEach(function(question){ + questions.forEach(function(question){ var option = document.createElement('option'); option.value = question.id; option.text = question.get('question'); @@ -336,6 +524,7 @@ var SolutionPartView = BaseView.extend( }, populateActivitySelect: function(select){ + if (select == null) return; var _this = this; utils.clearSelect(select); @@ -359,31 +548,77 @@ var SolutionPartView = BaseView.extend( $(select).selectpicker('refresh'); }, - renderMatFilter: function(el, width){ + populateAreaSelect: function(select){ + if (select == null) return; var _this = this; + utils.clearSelect(select); + + var option = document.createElement('option'); + option.value = -1; + option.text = gettext('no spatial restriction'); + select.appendChild(option); + this.areas.forEach(function(area){ + var option = document.createElement('option'); + option.value = area.id; + option.text = area.get('question'); + select.appendChild(option); + }) + }, + + populateProcessSelect: function(select, options){ + if (select == null) return; + var _this = this, + options = options || {}; + utils.clearSelect(select); + + var option = document.createElement('option'); + option.value = -1; + option.text = options.defaultOption || gettext('Select'); + select.appendChild(option); + this.processes.forEach(function(process){ + var option = document.createElement('option'); + option.value = process.id; + option.text = process.get('name'); + select.appendChild(option); + }) + }, + + renderMatFilter: function(el, options){ + if (el == null) return; + var _this = this, + options = options || {}; this.selectedMaterial = null; // select material var matSelect = document.createElement('div'); matSelect.classList.add('materialSelect'); var select = this.el.querySelector('.hierarchy-select'); var flowsInChildren = {}; - // count materials in parent, descending level (leafs first) - this.materials.models.reverse().forEach(function(material){ - var parent = material.get('parent'), - count = material.get('flow_count') + (flowsInChildren[material.id] || 0); - flowsInChildren[parent] = (!flowsInChildren[parent]) ? count: flowsInChildren[parent] + count; - }) + if (options.countChildren) { + // count materials in parent, descending level (leafs first) + this.materials.models.reverse().forEach(function(material){ + var parent = material.get('parent'), + count = material.get('flow_count') + (flowsInChildren[material.id] || 0); + flowsInChildren[parent] = (!flowsInChildren[parent]) ? count: flowsInChildren[parent] + count; + }) + } var hierarchicalSelect = this.hierarchicalSelect(this.materials, matSelect, { onSelect: function(model){ - el.dataset.selected = model.id; + el.dataset.selected = (model) ? model.id: null; + if (options.onSelect) options.onSelect(); }, - width: width, - defaultOption: gettext('Select'), + width: options.width, + defaultOption: options.defaultOption || gettext('Select'), label: function(model, option){ var compCount = model.get('flow_count'), - childCount = flowsInChildren[model.id] || 0, - label = model.get('name') + '(' + compCount + ' / ' + childCount + ')'; + label = model.get('name'); + + if (options.showCount && options.countChildren) { + var childCount = flowsInChildren[model.id] || 0; + label += ' (' + gettext('used ') + ' ' + compCount + 'x / ' + gettext('children used ') + ' ' + childCount + 'x)'; + } else if (options.showCount){ + label += ' (' + gettext('total of') + ' ' + compCount + ')'; + } return label; } }); @@ -394,11 +629,11 @@ var SolutionPartView = BaseView.extend( matFlowless.forEach(function(material){ var li = hierarchicalSelect.querySelector('li[data-value="' + material.id + '"]'); if (!li) return; - var a = li.querySelector('a'), - cls = (flowsInChildren[material.id] > 0) ? 'half': 'empty'; - a.classList.add(cls); + var a = li.querySelector('a'); + a.classList.add('empty'); }) el.appendChild(hierarchicalSelect); + el.select = hierarchicalSelect.select; }, addAffectedFlow: function(flow){ @@ -406,6 +641,7 @@ var SolutionPartView = BaseView.extend( html = document.getElementById('affected-flow-row-template').innerHTML, template = _.template(html), _this = this; + if (flow.tag) row.dataset.tag = flow.tag; row.innerHTML = template({}); row.classList.add('row'); var matSelect = row.querySelector('div[name="material"]'), @@ -423,12 +659,7 @@ var SolutionPartView = BaseView.extend( if (flow){ originSelect.value = flow['origin_activity']; destinationSelect.value = flow['destination_activity']; - destinationSelect.value = flow['destination_activity']; - li = matSelect.querySelector('li[data-value="' + flow['material'] + '"]'); - if(li){ - var matItem = li.querySelector('a'); - matItem.click(); - } + matSelect.select(flow['material']); } $(originSelect).selectpicker('refresh'); @@ -436,7 +667,114 @@ var SolutionPartView = BaseView.extend( removeBtn.addEventListener('click', function(){ _this.affectedDiv.removeChild(row); }) - } + }, + + fetchFlows: function(options){ + var displayLevel = 'activity', + _this = this, + material = this.affectedMaterialSelect.dataset.selected; + + var filterParams = { + materials: { ids: [material]}, + aggregation_level: { + origin: displayLevel, + destination: displayLevel + } + }; + + var flows = new GDSECollection([], { + apiTag: 'flows', + apiIds: [ this.caseStudy.id, this.keyflowId] + }); + + //this.loader.activate(); + var data = {}; + var loader = new utils.Loader(this.sankeyWrapper); + loader.activate(); + flows.postfetch({ + body: filterParams, + success: function(response){ + var idx = 0; + flows.forEach(function(flow){ + var origin = flow.get('origin'), + destination = flow.get('destination'); + // api aggregates flows and doesn't return an id + // generate an internal one to assign interactions + flow.set('id', idx); + idx++; + origin.color = utils.colorByName(origin.name); + }) + if (options.success){ + loader.deactivate(); + options.success(flows); + } + }, + error: function(error){ + loader.deactivate(); + _this.onError(error); + } + }) + }, + + drawSankey: function(){ + if (this.flowSankeyView != null) this.flowSankeyView.close(); + var displayLevel = 'activity', + _this = this; + + this.fetchFlows({ + success: function(flows){ + _this.flowSankeyView = new FlowSankeyView({ + el: _this.sankeyWrapper, + width: _this.sankeyWrapper.clientWidth - 10, + flows: flows, + height: 400, + originLevel: displayLevel, + destinationLevel: displayLevel, + renderStocks: false + }) + } + }) + }, + + linkSelected: function(e){ + // only actors atm + var data = e.detail, + _this = this; + + if (!Array.isArray(data)) data = [data]; + + data.forEach(function(d){ + var origin = d.get('origin'), + destination = d.get('destination'), + materials = d.get('materials'); + materials.forEach(function(m){ + var material = m.material; + _this.addAffectedFlow({ + origin_activity: origin.id, + destination_activity: destination.id, + material: m.material, + tag: d.id + }) + }) + }) + }, + + linkDeselected: function(e){ + // only actors atm + var data = e.detail, + _this = this; + + if (!Array.isArray(data)) data = [data]; + + data.forEach(function(d){ + var rows = _this.affectedDiv.querySelectorAll('div[data-tag="' + d.id + '"]'); + rows.forEach(function(row){ + _this.affectedDiv.removeChild(row); + }) + }) + }, + + }); return SolutionPartView; diff --git a/repair/js/views/strategy/setup-solutions-logic.js b/repair/js/views/strategy/setup-solutions-logic.js index 2e72bf396..41f1bf6cb 100644 --- a/repair/js/views/strategy/setup-solutions-logic.js +++ b/repair/js/views/strategy/setup-solutions-logic.js @@ -1,11 +1,11 @@ define(['views/common/baseview', 'underscore', 'collections/gdsecollection', - 'models/gdsemodel', 'views/strategy/setup-solution-part', 'views/strategy/setup-question', - 'collections/geolocations', 'visualizations/map', 'viewerjs', 'app-config', - 'utils/utils', 'muuri', 'visualizations/map', - 'bootstrap', 'viewerjs/dist/viewer.css', 'bootstrap-select'], + 'models/gdsemodel', 'views/strategy/setup-solution-part', + 'views/strategy/setup-question', 'views/strategy/setup-area', + 'collections/geolocations', 'viewerjs', 'app-config', + 'utils/utils', 'muuri', 'bootstrap', 'viewerjs/dist/viewer.css', 'bootstrap-select'], function(BaseView, _, GDSECollection, GDSEModel, SolutionPartView, QuestionView, - GeoLocations, Map, Viewer, config, utils, Muuri, Map){ + AreaView, GeoLocations, Viewer, config, utils, Muuri){ /** * * @author Christoph Franke @@ -56,11 +56,15 @@ var SolutionsLogicView = BaseView.extend( apiIds: [this.caseStudy.id, this.keyflowId], comparator: 'name' }); + this.processes = new GDSECollection([], { + apiTag: 'processes' + }); var promises = []; promises.push(this.activities.fetch()); promises.push(this.activityGroups.fetch()); promises.push(this.materials.fetch()); + promises.push(this.processes.fetch()); this.loader.activate(); Promise.all(promises).then(function(){ @@ -77,10 +81,10 @@ var SolutionsLogicView = BaseView.extend( */ events: { 'click #reload-solution-list': 'populateSolutions', - 'click #add-solution-part': 'addSolutionPart', + 'click #add-solution-part': 'showSchemes', + 'click #schemes-modal button.confirm': 'addSolutionPart', 'click #add-question': 'addQuestion', - 'click button[name="implementation-area"]': 'uploadArea', - 'click button[name="show-area"]': 'showArea' + 'click #add-area': 'addArea' }, /* @@ -93,15 +97,24 @@ var SolutionsLogicView = BaseView.extend( this.el.innerHTML = template({}); this.solutionPartModal = this.el.querySelector('#solution-part-modal'); - $(this.solutionPartModal).on('hide.bs.modal', function(){ - _this.editView.close(); - }) + //$(this.solutionPartModal).on('hide.bs.modal', function(){ + //_this.editView.close(); + //}) this.questionModal = this.el.querySelector('#question-modal'); $(this.questionModal).on('hide.bs.modal', function(){ _this.editView.close(); }) + this.areaModal = this.el.querySelector('#area-modal'); + $(this.areaModal).on('hide.bs.modal', function(){ + _this.editView.close(); + }) + + this.schemeSelectModal = this.el.querySelector('#schemes-modal'); + + new Viewer.default(this.el.querySelector('#scheme-legend')); + this.notesArea = this.el.querySelector('textarea[name="notes"]'); this.notesArea.addEventListener('change', function(){ _this.activeSolution.save({ documentation: this.value }, { @@ -124,7 +137,12 @@ var SolutionsLogicView = BaseView.extend( apiTag: 'questions', apiIds: [_this.caseStudy.id, _this.keyflowId, _this.activeSolution.id] }); - var promises = [_this.solutionParts.fetch(), _this.questions.fetch()]; + _this.areas = new GDSECollection([], { + apiTag: 'possibleImplementationAreas', + apiIds: [_this.caseStudy.id, _this.keyflowId, _this.activeSolution.id] + }); + var promises = [_this.solutionParts.fetch(), + _this.questions.fetch(), _this.areas.fetch()]; Promise.all(promises).then(function(){ _this.solutionParts.sort(); _this.renderSolution(); @@ -134,22 +152,19 @@ var SolutionsLogicView = BaseView.extend( this.populateSolutions(); this.solutionPartsPanel = this.el.querySelector('#solution-parts-panel'); this.questionsPanel = this.el.querySelector('#questions-panel'); - - this.implAreaText = this.el.querySelector('textarea[name="implementation-area"]'); - var mapDiv = this.el.querySelector('div[name="area-map"]'); - this.areaMap = new Map({ - el: mapDiv - }); - // map is rendered with wrong size, when tab is not visible -> update size when accessing tab - $('a[href="#area-tab"]').on('shown.bs.tab', function (e) { - _this.areaMap.map.updateSize(); - }); - this.areaMap.addLayer('implementation-area', { - stroke: '#aad400', - fill: 'rgba(170, 212, 0, 0.1)', - strokeWidth: 1, - zIndex: 0 - }); + this.areasPanel = this.el.querySelector('#areas-panel'); + + var schemeDivs = this.schemeSelectModal.querySelectorAll('.scheme-preview'); + schemeDivs.forEach(function(div){ + div.addEventListener('click', function(){ + schemeDivs.forEach(function(other){ + other.classList.remove('selected'); + }) + div.classList.add('selected'); + _this.selectScheme(div); + }) + }) + this.selectScheme(schemeDivs[0]); }, addSolutionPart: function(){ @@ -158,6 +173,7 @@ var SolutionsLogicView = BaseView.extend( apiTag: 'solutionparts', apiIds: [_this.caseStudy.id, _this.keyflowId, _this.activeSolution.id] }); + part.set('scheme', this.selectedScheme) function onConfirm(part){ part.save(null, { success: function(){ @@ -171,6 +187,21 @@ var SolutionsLogicView = BaseView.extend( this.editItem(part, onConfirm); }, + showSchemes: function(){ + var modal = this.schemeSelectModal; + $(modal).modal('show'); + }, + + selectScheme: function(schemeDiv){ + var title = schemeDiv.querySelector('label').innerHTML, + desc = schemeDiv.dataset.text, + src = schemeDiv.querySelector('img').src; + this.schemeSelectModal.querySelector('#selected-scheme-image').src = src; + this.schemeSelectModal.querySelector('#selected-scheme-description').innerHTML = desc; + this.schemeSelectModal.querySelector('#selected-scheme-title').innerHTML = title; + this.selectedScheme = schemeDiv.dataset.scheme; + }, + clonePart: function(model){ var _this = this, attr = Object.assign({}, model.attributes); @@ -196,7 +227,6 @@ var SolutionsLogicView = BaseView.extend( function onConfirm(question){ question.save(null, { success: function(){ - console.log(question) _this.questions.add(question); $(_this.questionModal).modal('hide'); _this.renderItem(question); @@ -207,6 +237,25 @@ var SolutionsLogicView = BaseView.extend( this.editItem(question, onConfirm); }, + addArea: function(){ + var _this = this, + area = new GDSEModel({}, { + apiTag: 'possibleImplementationAreas', + apiIds: [_this.caseStudy.id, _this.keyflowId, _this.activeSolution.id] + }); + function onConfirm(question){ + question.save(null, { + success: function(){ + _this.areas.add(area); + $(_this.areaModal).modal('hide'); + _this.renderItem(area); + }, + error: _this.onError + }); + } + this.editItem(area, onConfirm); + }, + /* fill selection with solutions */ populateSolutions: function(){ var _this = this, @@ -246,34 +295,44 @@ var SolutionsLogicView = BaseView.extend( panelItem.style.position = 'absolute'; panelItem.dataset.id = model.id; itemContent.classList.add('noselect', 'item-content'); - var name = (type === 'questions') ? model.get('question') : model.get('name'); + var name = (type === 'solutionparts') ? model.get('name') : model.get('question'); + if (type === 'questions'){ + var supp = model.get('is_absolute') ? gettext('absolute change') : gettext('relative change'); + name += ' (' + supp + ')'; + } itemContent.innerHTML = template({ name: name }); - var grid = (type === 'solutionparts') ? this.solutionPartsGrid: this.questionsGrid, - modal = (type === 'solutionparts') ? this.solutionPartModal: this.questionModal; - - + var grid = (type === 'solutionparts') ? this.solutionPartsGrid: + (type === 'possibleImplementationAreas') ? this.areasGrid: + this.questionsGrid, + modal = (type === 'solutionparts') ? this.solutionPartModal: + (type === 'possibleImplementationAreas') ? this.areaModal: + this.questionModal; var buttonGroup = itemContent.querySelector(".button-box"), editBtn = buttonGroup.querySelector("button.edit"), removeBtn = buttonGroup.querySelector("button.remove"); - - var cloneBtn = document.createElement('button'), - iconSpan = document.createElement('span'); - cloneBtn.classList.add('square','inverted', 'btn','btn-secondary'); - cloneBtn.title = gettext('clone item'); - iconSpan.classList.add('glyphicon', 'glyphicon-duplicate'); - cloneBtn.appendChild(iconSpan); - buttonGroup.appendChild(cloneBtn); - cloneBtn.addEventListener('click', function(){ - _this.clonePart(model); - }) - + if (type === 'solutionparts'){ + var cloneBtn = document.createElement('button'), + iconSpan = document.createElement('span'); + cloneBtn.classList.add('square','inverted', 'btn','btn-secondary'); + cloneBtn.title = gettext('clone item'); + iconSpan.classList.add('glyphicon', 'glyphicon-duplicate'); + cloneBtn.appendChild(iconSpan); + buttonGroup.appendChild(cloneBtn); + cloneBtn.addEventListener('click', function(){ + _this.clonePart(model); + }) + } editBtn.addEventListener('click', function(){ function onConfirm(model){ model.save(null, { success: function(){ - var name = (type === 'questions') ? model.get('question') : model.get('name'); + var name = (type === 'solutionparts') ? model.get('name') : model.get('question'); + if (type === 'questions'){ + var supp = model.get('is_absolute') ? gettext('absolute change') : gettext('relative change'); + name += ' (' + supp + ')'; + } itemContent.querySelector('label[name="name"]').innerHTML = name; $(modal).modal('hide'); }, @@ -322,22 +381,32 @@ var SolutionsLogicView = BaseView.extend( editItem: function(model, onConfirm){ var _this = this, type = model.apiTag, - modal = (type === 'solutionparts') ? this.solutionPartModal: this.questionModal, - template = (type === 'solutionparts') ? 'solution-part-template': 'question-template', - View = (type === 'solutionparts') ? SolutionPartView: QuestionView, + modal = (type === 'solutionparts') ? this.solutionPartModal: + (type === 'possibleImplementationAreas') ? this.areaModal: + this.questionModal; + View = (type === 'solutionparts') ? SolutionPartView: + (type === 'possibleImplementationAreas') ? AreaView: + QuestionView; el = modal.querySelector('.modal-body'), confirmBtn = modal.querySelector('.confirm'); $(modal).modal('show'); this.editView = new View({ model: model, - template: template, el: el, materials: this.materials, activityGroups: this.activityGroups, activities: this.activities, questions: this.questions, - solutionParts: this.solutionParts + solutionParts: this.solutionParts, + areas: this.areas, + processes: this.processes, + caseStudy: this.caseStudy, + keyflowId: this.keyflowId }) + if (type === 'possibleImplementationAreas') + $(modal).on('shown.bs.modal', function (e) { + _this.editView.areaMap.map.updateSize(); + }); confirmBtn = utils.removeEventListeners(confirmBtn); confirmBtn.addEventListener('click', function(){ _this.editView.applyInputs(); @@ -353,18 +422,18 @@ var SolutionsLogicView = BaseView.extend( }) }, - renderSolution: function(solution, parts, questions){ + renderSolution: function(solution){ var _this = this, - solution = this.activeSolution, - parts = this.solutionParts, - questions = this.questions; + solution = this.activeSolution; if (!solution) return; if (this.solutionPartsGrid) this.solutionPartsGrid.destroy(); if (this.questionsGrid) this.questionsGrid.destroy(); + if (this.areasGrid) this.areasGrid.destroy(); this.solutionPartsPanel.innerHTML = ''; this.questionsPanel.innerHTML = ''; + this.areasPanel.innerHTML = ''; this.solutionPartsGrid = new Muuri(this.solutionPartsPanel, { items: '.panel-item', @@ -380,73 +449,15 @@ var SolutionsLogicView = BaseView.extend( items: '.panel-item', dragEnabled: false }) + this.areasGrid = new Muuri(this.areasPanel, { + items: '.panel-item', + dragEnabled: false + }) this.solutionPartsGrid.on('dragReleaseEnd', this.uploadPriorities); this.el.querySelector('#solution-logic-content').style.visibility = 'visible'; - this.renderItems(parts); - this.renderItems(questions); - this.notesArea.value = solution.get('documentation'); - - this.areaMap.clearLayer('implementation-area'); - var implArea = solution.get('possible_implementation_area') || ''; - if(implArea) implArea = JSON.stringify(implArea); - this.implAreaText.value = implArea; - this.showArea(); - }, - - checkGeoJSON: function(geoJSONTxt){ - try { - var geoJSON = JSON.parse(geoJSONTxt); - } - catch(err) { - this.alert(err); - return; - } - if (!geoJSON.coordinates && !geoJSON.type) { - this.alert(gettext('GeoJSON needs attributes "type" and "coordinates"')); - } - if (!['multipolygon', 'polygon'].includes(geoJSON.type.toLowerCase())){ - this.alert(gettext('type has to be MultiPolygon or Polygon')); - return; - } - - return geoJSON; - }, - - showArea: function(){ - var implArea = this.implAreaText.value; - if (!implArea) return; - - var geoJSON = this.checkGeoJSON(implArea); - if (!geoJSON) return; - - this.areaMap.clearLayer('implementation-area'); - try { - var poly = this.areaMap.addPolygon(geoJSON.coordinates, { - projection: this.projection, - layername: 'implementation-area', - tooltip: gettext('Focus area'), - type: geoJSON.type.toLowerCase() - }); - } - catch(err) { - this.alert(err); - return; - } - this.areaMap.centerOnPolygon(poly, { projection: this.projection }); - }, - - uploadArea: function(){ - var geoJSON = this.checkGeoJSON(this.implAreaText.value); - if (!geoJSON) return; - var _this = this; - - this.activeSolution.save({ 'possible_implementation_area': geoJSON },{ - success: function(){ - _this.alert(gettext('Upload successful'), gettext('Success')); - }, - error: _this.onError, - patch: true - }) + this.renderItems(this.solutionParts); + this.renderItems(this.questions); + this.renderItems(this.areas); } }); return SolutionsLogicView; diff --git a/repair/js/views/strategy/strategy.js b/repair/js/views/strategy/strategy.js index e52a6ccab..201dfd81b 100644 --- a/repair/js/views/strategy/strategy.js +++ b/repair/js/views/strategy/strategy.js @@ -84,6 +84,11 @@ var StrategyView = BaseView.extend( apiTag: 'questions', apiIds: [_this.caseStudy.id, _this.keyflowId, solution.id] }); + solution.areas = new GDSECollection([], { + apiTag: 'possibleImplementationAreas', + apiIds: [_this.caseStudy.id, _this.keyflowId, solution.id] + }); + deferreds.push(solution.areas.fetch()); deferreds.push(solution.questions.fetch()); solution.parts = new GDSECollection([], { apiTag: 'solutionparts', @@ -277,7 +282,16 @@ var StrategyView = BaseView.extend( solution: solution, stakeholderCategories: this.stakeholderCategories, questions: solution.questions, - solutionparts: solution.parts + solutionparts: solution.parts, + implementationAreas: solution.areas + }); + + this.drawings = {}; + + this.areaSelect = this.solutionModal.querySelector('select[name="implementation-areas"]'); + this.areaSelect.addEventListener('change', function(){ + _this.setupArea(solutionImpl); + _this.mapEl.classList.remove('disabled'); }); var stakeholderSelect = this.solutionModal.querySelector('#strategy-stakeholders'), @@ -288,46 +302,28 @@ var StrategyView = BaseView.extend( } } $(stakeholderSelect).selectpicker(); - + this.mapEl = document.getElementById('editor-map'); if (this.editorMapView) this.editorMapView.close(); + var loader = new utils.Loader(this.areaSelect.parentElement, {disable: true}); + loader.activate(); this.editorMapView = new BaseMapView({ template: 'base-maps-template', - el: document.getElementById('editor-map'), + el: this.mapEl, caseStudy: this.caseStudy, onReady: function(){ _this.setupEditor(solutionImpl); + _this.areaSelect.parentElement.classList.remove('disabled'); + loader.deactivate(); + _this.editorMapView.map.labelZoom = 13; } }); - var hasActorsToPick = false; - solution.parts.forEach(function(part){ - if (part.get('implements_new_flow')) hasActorsToPick = true; - }) - var actorsLi = this.solutionModal.querySelector('a[href="#actors-tab"]'), editorLi = this.solutionModal.querySelector('a[href="#strategy-area-tab"]'); // update map after switching to tab to fit width and height of wrapping div $(editorLi).on('shown.bs.tab', function () { if (_this.editorMap) _this.editorMap.map.updateSize(); }); - if (hasActorsToPick){ - this.pickedActors = {}; - solutionImpl.get('picked_actors').forEach(function(pick){ - _this.pickedActors[pick.solutionpart] = pick.actor; - }) - this.renderActorMap('actor-map', solutionImpl); - $(actorsLi).on('shown.bs.tab', function () { - _this.actorMap.map.updateSize(); - }); - - var requestSelect = this.solutionModal.querySelector('select[name="map-request"]'); - requestSelect.addEventListener('change', function(){ - var picked = _this.pickedActors[this.value]; - _this.renderSelectableActors(solution.parts.get(this.value), picked); - }); - } - else - actorsLi.style.display = 'none'; $(this.solutionModal).modal('show'); @@ -344,20 +340,33 @@ var StrategyView = BaseView.extend( } solutionImpl.set('participants', stakeholderIds); // drawn features - var features = _this.editorMap.getFeatures('drawing'); - if (features.length > 0){ - var geometries = []; - features.forEach(function(feature) { - var geom = feature.getGeometry().transform(_this.editorMap.mapProjection, _this.projection); - geometries.push(geom); - }); - var geoCollection = new ol.geom.GeometryCollection(geometries), - geoJSON = new ol.format.GeoJSON(), - geoJSONText = geoJSON.writeGeometry(geoCollection); - solutionImpl.set('geom', geoJSONText); + var areas = [] + for (var areaId in _this.drawings){ + var features = _this.drawings[areaId], + geoJSONText = null; + if (features.length > 0){ + var multi = new ol.geom.MultiPolygon(); + features.forEach(function(feature) { + var geom = feature.getGeometry().transform(_this.editorMap.mapProjection, _this.projection); + if (geom.getType() == 'MultiPolygon'){ + geom.getPolygons().forEach(function(poly){ + multi.appendPolygon(poly); + }) + } else { + multi.appendPolygon(geom); + } + }); + var geoJSON = new ol.format.GeoJSON(), + geoJSONText = geoJSON.writeGeometry(multi); + } + var implArea = { + geom: geoJSONText, + possible_implementation_area: areaId + } + areas.push(implArea) } - else - solutionImpl.set('geom', null); + + solutionImpl.set('areas', areas); var quantityInputs = _this.solutionModal.querySelectorAll('input[name="quantity"]'), quantities = []; @@ -371,17 +380,6 @@ var StrategyView = BaseView.extend( solutionImpl.set('quantities', quantities); - if (hasActorsToPick){ - var picked = []; - for (var partId in _this.pickedActors){ - picked.push({ - solutionpart: partId, - actor: _this.pickedActors[partId] - }) - } - solutionImpl.set('picked_actors', picked); - } - var notes = _this.solutionModal.querySelector('textarea[name="description"]').value; solutionImpl.set('note', notes); solutionImpl.save(null, { @@ -403,74 +401,13 @@ var StrategyView = BaseView.extend( }) }, - // render the administrative locations of all actors of activity in solutionpart - renderSelectableActors: function(solutionpart, picked){ - var _this = this, - activityId = solutionpart.get('new_target_activity'); - if (!activityId) return; - - var cache = this.activityCache[activityId]; - - function renderActors(actors, locations){ - _this.actorMap.clearLayer('pickable-actors'); - if (cache.actors.length === 0) return; - cache.locations.forEach(function(loc){ - var properties = loc.get('properties'), - actor = cache.actors.get(properties.actor), - geom = loc.get('geometry'); - if (geom) { - _this.actorMap.addGeometry(geom.get('coordinates'), { - projection: _this.projection, - layername: 'pickable-actors', - tooltip: actor.get('name'), - label: actor.get('name'), - id: actor.id, - type: 'Point' - }); - } - }) - if (picked) - _this.actorMap.selectFeature('pickable-actors', picked); - } - - if (!cache){ - this.activityCache[activityId] = cache = {}; - cache.actors = new GDSECollection([], { - apiTag: 'actors', - apiIds: [this.caseStudy.id, this.keyflowId] - }) - cache.actors.fetch({ - data: { activity: activityId, included: "True" }, - processData: true, - success: function(){ - if (cache.actors.length === 0) return; - var actorIds = cache.actors.pluck('id'); - cache.locations = new GeoLocations([],{ - apiTag: 'adminLocations', - apiIds: [_this.caseStudy.id, _this.keyflowId] - }); - var data = {}; - data['actor__in'] = actorIds.toString(); - cache.locations.fetch({ - data: data, - processData: true, - success: function(){ - renderActors(cache.actors, cache.locations) - }, - error: _this.onError - }) - }, - error: _this.onError - }) - } - else renderActors(cache.actors, cache.locations); - }, /* * render the map with the drawn polygons into the solution item */ renderSolutionPreviewMap: function(solutionImpl, item){ var divid = 'solutionImpl' + solutionImpl.id, - _this = this; + _this = this, + areas = solutionImpl.get('areas'); var mapDiv = item.querySelector('.olmap'); mapDiv.id = divid; mapDiv.innerHTML = ''; @@ -480,13 +417,18 @@ var StrategyView = BaseView.extend( showControls: false, enableDrag: false }); - var geom = solutionImpl.get('geom'); - if (geom != null){ + var geometries = []; + areas.forEach(function(area){ + if (!area.geom) return; + geometries.push(area.geom) + }) + if (geometries.length > 0){ previewMap.addLayer('geometry'); - geom.geometries.forEach(function(g){ - previewMap.addGeometry(g.coordinates, { - projection: _this.projection, layername: 'geometry', - type: g.type + geometries.forEach(function(geom){ + previewMap.addGeometry(geom.coordinates, { + projection: _this.projection, + layername: 'geometry', + type: geom.type }); }) previewMap.centerOnLayer('geometry'); @@ -496,50 +438,6 @@ var StrategyView = BaseView.extend( } }, - renderActorMap: function(divid, solutionImpl) { - var el = document.getElementById(divid), - _this = this; - - // calculate (min) height - var height = document.body.clientHeight * 0.6; - el.style.height = height + 'px'; - // remove old map - if (this.actorMap){ - this.actorMap.map.setTarget(null); - this.actorMap.map = null; - this.actorMap = null; - } - this.actorMap = new Map({ - el: el - }); - - if (this.focusPoly){ - this.actorMap.centerOnPolygon(this.focusPoly, { projection: this.projection }); - }; - var requestSelect = this.solutionModal.querySelector('select[name="map-request"]'); - this.actorMap.addLayer('pickable-actors', { - stroke: 'black', - fill: 'red', - strokeWidth: 1, - zIndex: 1, - icon: '/static/img/map-marker-red.svg', - anchor: [0.5, 1], - labelColor: '#111', - labelOutline: 'white', - select: { - selectable: true, - onChange: function(feature){ - _this.pickedActors[requestSelect.value] = feature[0].id; - }, - multi: false, - icon: '/static/img/map-marker-yellow.svg', - anchor: [0.5, 1], - labelColor: 'yellow', - labelOutline: '#111' - } - }); - }, - /* * render the map to draw on inside the solution modal */ @@ -553,6 +451,9 @@ var StrategyView = BaseView.extend( toolsDiv.innerHTML = template(); this.el.querySelector('#base-map').prepend(toolsDiv); + this.drawingTools = this.el.querySelector('.drawing-tools'); + this.drawingTools.style.visibility = 'hidden'; + this.editorMap = this.editorMapView.map; if (this.focusPoly){ @@ -572,59 +473,35 @@ var StrategyView = BaseView.extend( zIndex: 998 }); - var geom = solutionImpl.get('geom'); - this.editorMap.addLayer('drawing', { select: { selectable: true }, strokeWidth: 3, zIndex: 1000 }); - if (geom){ - geom.geometries.forEach(function(g){ - _this.editorMap.addGeometry(g.coordinates, { - projection: _this.projection, layername: 'drawing', - type: g.type - }); - }) - _this.editorMap.centerOnLayer('drawing'); - } - var drawingTools = this.el.querySelector('.drawing-tools'), - removeBtn = drawingTools.querySelector('.remove'), - tools = drawingTools.querySelectorAll('.tool'), - togglePossibleArea = this.el.querySelector('input[name="show-possible-area"]'); + this.activityNames = []; - var implArea = solution.get('possible_implementation_area') || ''; - if(implArea) { - var mask = solution.get('edit_mask'); - var maskArea = this.editorMap.addPolygon(mask.coordinates, { - projection: this.projection, - layername: 'mask', - type: mask.type, - tooltip: gettext('possible implementation area') - }); - var area = this.editorMap.addPolygon(implArea.coordinates, { - projection: this.projection, - layername: 'implementation-area', - type: implArea.type, - tooltip: gettext('possible implementation area') - }); - this.editorMap.centerOnPolygon(area, { projection: this.projection }); - } else { - togglePossibleArea.parentElement.style.display = 'none'; - } + var removeBtn = this.drawingTools.querySelector('.remove'), + tools = this.drawingTools.querySelectorAll('.tool'), + togglePossibleArea = this.el.querySelector('input[name="show-possible-area"]'); togglePossibleArea.addEventListener('change', function(){ _this.editorMap.setVisible('implementation-area', this.checked); _this.editorMap.setVisible('mask', this.checked); }) + function onDrawing(){ + var areaId = _this.areaSelect.value; + _this.drawings[areaId] = _this.editorMap.getFeatures('drawing'); + } + removeBtn.addEventListener('click', function(){ _this.editorMap.removeSelectedFeatures('drawing'); + onDrawing(); }) function toolChanged(){ - var checkedTool = drawingTools.querySelector('.active').querySelector('input'), + var checkedTool = _this.drawingTools.querySelector('.active').querySelector('input'), type = checkedTool.dataset.tool, selectable = false, useDragBox = false, @@ -643,12 +520,11 @@ var StrategyView = BaseView.extend( _this.editorMap.toggleDrawing('drawing', { type: type, freehand: freehand, - intersectionLayer: (implArea) ? 'implementation-area': null + intersectionLayer: 'implementation-area', + onDrawEnd: onDrawing }); _this.editorMap.enableDragBox('drawing'); } - // seperate dragbox tool disabled, doesn't work with touch - //if (type === 'DragBox') useDragBox = true; _this.editorMap.enableSelect('drawing', selectable); _this.editorMap.enableDragBox('drawing', useDragBox); removeBtn.style.display = (removeActive) ? 'block' : 'none'; @@ -664,6 +540,116 @@ var StrategyView = BaseView.extend( } }, + setupArea: function(solutionImpl){ + var _this = this; + this.drawingTools.style.visibility = 'visible'; + this.editorMap.clearLayer('mask'); + this.editorMap.clearLayer('implementation-area'); + this.editorMap.clearLayer('drawing'); + + this.activityNames.forEach(function(name){ + _this.editorMap.removeLayer('actors' + name); + _this.removeFromLegend(name); + }); + this.activityNames = []; + + var solution = this.solutions.get(solutionImpl.get('solution')), + areaId = this.areaSelect.value; + possImplArea = solution.areas.get(areaId), + implAreas = solutionImpl.get('areas'), + implArea = implAreas.find(function(area){ + return area.possible_implementation_area == areaId; + }); + var mask = possImplArea.get('edit_mask'), + maskArea = this.editorMap.addPolygon(mask.coordinates, { + projection: this.projection, + layername: 'mask', + type: mask.type + }); + var geom = possImplArea.get('geom'); + var area = this.editorMap.addPolygon(geom.coordinates, { + projection: this.projection, + layername: 'implementation-area', + type: geom.type + //tooltip: gettext('possible implementation area') + }); + this.editorMap.centerOnPolygon(area, { projection: this.projection }); + + if (this.drawings[areaId]){ + this.editorMap.addFeatures('drawing', this.drawings[areaId]) + } + else if (implArea && implArea.geom){ + _this.editorMap.addGeometry(implArea.geom.coordinates, { + projection: _this.projection, layername: 'drawing', + type: implArea.geom.type + }); + _this.editorMap.centerOnLayer('drawing'); + } + + var actorIds = possImplArea.get('affected_actors'); + var actors = new GDSECollection([], { + apiTag: 'actors', + apiIds: [this.caseStudy.id, this.keyflowId] + }); + var locations = new GeoLocations([], { + apiTag: 'adminLocations', + apiIds: [this.caseStudy.id, this.keyflowId] + }); + var promises = []; + if (actorIds.length > 0){ + this.loader.activate(); + promises.push(actors.postfetch({ + body: {id: actorIds.join(',')}, + error: _this.onError + })); + promises.push(locations.postfetch({ + body: {actor__in: actorIds.join(',')}, + error: _this.onError + })); + } + + function bgColor(color){ + if(color.length < 5) { + color += color.slice(1); + } + return (color.replace('#','0x')) > (0xffffff/2) ? '#333' : '#fff'; + }; + + Promise.all(promises).then(function(){ + var activityNames = [...new Set(actors.pluck('activity_name'))]; + activityNames.forEach(function(activityName){ + var color = utils.colorByName(activityName), + layername = 'actors' + activityName; + _this.editorMap.addLayer(layername, { + stroke: 'black', + fill: color, + labelColor: color, + labelOutline: bgColor(color), + labelFontSize: '12px', + labelOffset: 15, + strokeWidth: 1, + zIndex: 1001 + }); + _this.addToLegend(activityName, color); + _this.activityNames.push(activityName); + }) + locations.forEach(function(location){ + var geom = location.get('geometry'), + actor = actors.get(location.get('properties').actor), + activityName = actor.get('activity_name'); + if (!geom && !geom.get('coordinates')) return; + _this.editorMap.addGeometry(geom.get('coordinates'), { + projection: _this.projection, + tooltip: actor.get('name'), + layername: 'actors' + activityName, + label: actor.get('name'), + type: 'Point' + }); + }) + _this.loader.deactivate(); + }) + }, + saveOrder: function(){ var items = this.strategyGrid.getItems(), i = 0, @@ -673,7 +659,38 @@ var StrategyView = BaseView.extend( _this.solutionsInStrategy.get(id).save({ priority: i }, { patch: true }) i++; }); - } + }, + + addToLegend: function(activityName, color){ + var legend = this.el.querySelector('#legend'), + itemsDiv = legend.querySelector('.items'), + legendDiv = document.createElement('div'), + circle = document.createElement('div'), + textDiv = document.createElement('div'), + head = document.createElement('b'), + img = document.createElement('img'); + legendDiv.dataset['activity'] = activityName; + textDiv.innerHTML = gettext('actors') + ' ' + activityName; + textDiv.style.overflow = 'hidden'; + textDiv.style.textOverflow = 'ellipsis'; + legendDiv.appendChild(circle); + legendDiv.appendChild(textDiv); + circle.style.backgroundColor = color; + circle.style.float = 'left'; + circle.style.width = '20px'; + circle.style.height = '20px'; + circle.style.marginRight = '5px'; + circle.style.borderRadius = '10px'; + legendDiv.style.marginBottom = '10px'; + itemsDiv.prepend(legendDiv); + }, + + removeFromLegend: function(activityName){ + var legend = this.el.querySelector('#legend'), + itemsDiv = legend.querySelector('.items'); + entry = itemsDiv.querySelector('div[data-activity="' + activityName + '"]'); + itemsDiv.removeChild(entry); + }, }); return StrategyView; diff --git a/repair/js/views/study-area/charts.js b/repair/js/views/study-area/charts.js index 63d574f51..47c6bae7b 100644 --- a/repair/js/views/study-area/charts.js +++ b/repair/js/views/study-area/charts.js @@ -307,6 +307,7 @@ var BaseChartsView = BaseView.extend( var image = imgInput.files[0], name = this.el.querySelector('#chart-name').value; + console.log(image) var data = { name: name, diff --git a/repair/js/views/study-area/stakeholders.js b/repair/js/views/study-area/stakeholders.js index 5cb1adc44..e0bfe1877 100644 --- a/repair/js/views/study-area/stakeholders.js +++ b/repair/js/views/study-area/stakeholders.js @@ -26,21 +26,22 @@ var StakeholdersView = BaseView.extend( initialize: function(options){ StakeholdersView.__super__.initialize.apply(this, [options]); var _this = this; + _.bindAll(this, 'initStakeholders'); this.template = options.template; this.caseStudy = options.caseStudy; - var caseStudyId = this.caseStudy.id; + this.caseStudyId = this.caseStudy.id; this.mode = options.mode || 0; this.stakeholderCategories = new GDSECollection([], { apiTag: 'stakeholderCategories', - apiIds: [ caseStudyId ] + apiIds: [ _this.caseStudyId ] }); this.stakeholderCategories.fetch({ success: function(stakeholderCategories){ - _this.initStakeholders(stakeholderCategories, caseStudyId); + _this.initStakeholders(); }, error: _this.onError }); @@ -54,14 +55,14 @@ var StakeholdersView = BaseView.extend( 'click #add-category-button': 'addCategory', }, - initStakeholders: function(stakeholderCategories, caseStudyId){ + initStakeholders: function(){ var _this = this; var promises = []; - stakeholderCategories.forEach(function(category){ + this.stakeholderCategories.forEach(function(category){ var stakeholders = new GDSECollection([], { apiTag: 'stakeholders', - apiIds: [ caseStudyId, category.id ] + apiIds: [ _this.caseStudyId, category.id ] }); promises.push(stakeholders.fetch({ @@ -295,7 +296,8 @@ var StakeholdersView = BaseView.extend( // with the same attributes function onConfirm(name){ _this.stakeholderCategories.create({name: name}, { - success: _this.render, + // ToDo: atm we just fetch and rerender everything (because lazy?) + success: _this.initStakeholders, error: _this.onError, wait: true }); diff --git a/repair/js/views/targets/ranking-objectives.js b/repair/js/views/targets/ranking-objectives.js index 60148eabf..122c4ffd3 100644 --- a/repair/js/views/targets/ranking-objectives.js +++ b/repair/js/views/targets/ranking-objectives.js @@ -3,155 +3,165 @@ define(['underscore','views/common/baseview', 'collections/gdsecollection', 'muuri'], function(_, BaseView, GDSECollection, Muuri){ +/** +* +* @author Christoph Franke +* @name module:views/RankingObjectivesView +* @augments Backbone.View +*/ +var RankingObjectivesView = BaseView.extend( + /** @lends module:views/RankingObjectivesView.prototype */ + { + /** + * render workshop view on ranking the objectives in a keyflow + * + * @param {Object} options + * @param {HTMLElement} options.el element the view will be rendered in + * @param {string} options.template id of the script element containing the underscore template to render this view + * @param {module:models/CaseStudy} options.caseStudy the casestudy of the keyflow + * @param {module:models/CaseStudy} options.keyflowId the keyflow the objectives belong to * - * @author Christoph Franke - * @name module:views/RankingObjectivesView - * @augments Backbone.View + * @constructs + * @see http://backbonejs.org/#View */ - var RankingObjectivesView = BaseView.extend( - /** @lends module:views/RankingObjectivesView.prototype */ - { + initialize: function(options){ + RankingObjectivesView.__super__.initialize.apply(this, [options]); + var _this = this; + _.bindAll(this, 'renderObjective'); - /** - * render workshop view on ranking the objectives in a keyflow - * - * @param {Object} options - * @param {HTMLElement} options.el element the view will be rendered in - * @param {string} options.template id of the script element containing the underscore template to render this view - * @param {module:models/CaseStudy} options.caseStudy the casestudy of the keyflow - * @param {module:models/CaseStudy} options.keyflowId the keyflow the objectives belong to - * - * @constructs - * @see http://backbonejs.org/#View - */ - initialize: function(options){ - RankingObjectivesView.__super__.initialize.apply(this, [options]); - var _this = this; - _.bindAll(this, 'renderObjective'); + this.template = options.template; + this.caseStudy = options.caseStudy; + this.keyflowId = options.keyflowId; + this.keyflowName = options.keyflowName; + this.userObjectives = options.userObjectives; + this.aims = options.aims; - this.template = options.template; - this.caseStudy = options.caseStudy; - this.keyflowId = options.keyflowId; - this.keyflowName = options.keyflowName; - this.userObjectives = options.userObjectives; - this.aims = options.aims; + this.render(); + }, - this.render(); - }, + /* + * dom events (managed by jquery) + */ + events: { + 'click .reset': 'reset' + }, - /* - * dom events (managed by jquery) - */ - events: { - 'click .add-target': 'addTarget' - }, + /* + * render the view + */ + render: function(){ + var _this = this, + html = document.getElementById(this.template).innerHTML, + template = _.template(html), + title; + if (this.keyflowId == -1) + title = gettext("Ranking general objectives") + else + title = gettext("Ranking objectives for the keyflow ") + "" + this.keyflowName + ""; + this.el.innerHTML = template({ title: title }); + var panel = this.el.querySelector('.item-panel'); + this.grid = new Muuri(panel, { + items: '.panel-item', + dragAxis: 'y', + layoutDuration: 400, + layoutEasing: 'ease', + dragEnabled: true, + dragSortInterval: 0, + dragReleaseDuration: 400, + dragReleaseEasing: 'ease' + }); - /* - * render the view - */ - render: function(){ - var _this = this, - html = document.getElementById(this.template).innerHTML, - template = _.template(html), - title; - if (this.keyflowId == -1) - title = gettext("Ranking general objectives") - else - title = gettext("Ranking objectives for the keyflow ") + "" + this.keyflowName + ""; - this.el.innerHTML = template({ title: title }); - var panel = this.el.querySelector('.item-panel'); - this.grid = new Muuri(panel, { - items: '.panel-item', - dragAxis: 'y', - layoutDuration: 400, - layoutEasing: 'ease', - dragEnabled: true, - dragSortInterval: 0, - dragReleaseDuration: 400, - dragReleaseEasing: 'ease' - }); + this.grid.on('dragReleaseEnd', function(item) { + var id = item.getElement().dataset['id']; + _this.uploadPriorities(id); + }); + // render objectives with set priorities on top + this.userObjectives.forEach(function(objective){ + if (objective.get('priority') >= 0) + _this.renderObjective(objective) + }); + this.userObjectives.forEach(function(objective){ + if (objective.get('priority') < 0) + _this.renderObjective(objective) + }); - this.grid.on('dragReleaseEnd', function(item) { - var id = item.getElement().dataset['id']; - _this.uploadPriorities(id); - }); - // render objectives with set priorities on top - this.userObjectives.forEach(function(objective){ - if (objective.get('priority') >= 0) - _this.renderObjective(objective) - }); - this.userObjectives.forEach(function(objective){ - if (objective.get('priority') < 0) - _this.renderObjective(objective) - }); + var btns = this.el.querySelectorAll('button:not(.reset)'); + _.each(btns, function(button){ + button.style.display = 'none'; + }); + }, - var btns = this.el.querySelectorAll('button'); - _.each(btns, function(button){ - button.style.display = 'none'; - }); - }, + reset: function(){ + this.userObjectives.forEach(function(objective){ + objective.set('priority', -1); + }); + this.uploadPriorities(); + this.render(); + }, - renderObjective: function(objective){ - var html = document.getElementById('panel-item-template').innerHTML, - template = _.template(html), - panelItem = document.createElement('div'), - itemContent = document.createElement('div'), - priority = objective.get('priority'), - _this = this; - var aim = this.aims.get(objective.get('aim')); - panelItem.classList.add('panel-item'); - panelItem.classList.add('draggable'); - panelItem.style.position = 'absolute'; - panelItem.dataset.id = objective.id; - itemContent.classList.add('noselect', 'item-content'); - itemContent.innerHTML = template({ name: aim.get('text') }); - panelItem.appendChild(itemContent); - this.grid.add(panelItem); - - if (priority < 1) - panelItem.style.background = '#d1d1d1'; - else { - var overlay = panelItem.querySelector('.overlay'); - overlay.style.display = 'inline-block'; - overlay.innerHTML = '#' + priority; - } - var desc = aim.get('description') || '-'; + renderObjective: function(objective){ + var html = document.getElementById('panel-item-template').innerHTML, + template = _.template(html), + panelItem = document.createElement('div'), + itemContent = document.createElement('div'), + priority = objective.get('priority'), + _this = this; + var aim = this.aims.get(objective.get('aim')); + panelItem.classList.add('panel-item'); + panelItem.classList.add('draggable'); + panelItem.style.position = 'absolute'; + panelItem.dataset.id = objective.id; + itemContent.classList.add('noselect', 'item-content'); + itemContent.innerHTML = template({ name: aim.get('text') }); + panelItem.appendChild(itemContent); + this.grid.add(panelItem); + var label = itemContent.querySelector('label[name="name"]'); + label.style.maxWidth = 'calc(100% - 50px)'; + if (priority < 1) + panelItem.style.background = '#d1d1d1'; + else { + var overlay = panelItem.querySelector('.overlay'); + overlay.style.display = 'inline-block'; + overlay.innerHTML = '#' + priority; + } + var desc = aim.get('description'); + if (desc){ $(panelItem).popover({ trigger: "hover", container: 'body', - //placement: 'bottom', content: desc.replace(/\n/g, "
"), html: true }); - }, - - uploadPriorities: function(draggedId){ - var items = this.grid.getItems(), - priority = 1, - _this = this; - items.forEach(function(item){ - var el = item.getElement(), - id = el.dataset.id, - objective = _this.userObjectives.get(id); - // only update priorities for the dragged item and those whose - // priority was assigned before - if (draggedId == id || parseInt(objective.get('priority')) >= 1) { - el.style.background = null; - objective.set('priority', priority); - objective.save(); - var overlay = el.querySelector('.overlay'); - overlay.style.display = 'inline-block'; - overlay.innerHTML = '#' + priority; - priority++; - } - }) - _this.userObjectives.sort(); - //_this.userObjectives.trigger('priorities-changed') } + }, + + uploadPriorities: function(draggedId){ + var items = this.grid.getItems(), + priority = 1, + _this = this; + items.forEach(function(item){ + var el = item.getElement(), + id = el.dataset.id, + objective = _this.userObjectives.get(id); + // only update priorities for the dragged item and those whose + // priority was assigned before + if (draggedId == id || parseInt(objective.get('priority')) >= 1) { + el.style.background = null; + objective.set('priority', priority); + objective.save(); + var overlay = el.querySelector('.overlay'); + overlay.style.display = 'inline-block'; + overlay.innerHTML = '#' + priority; + priority++; + } + }) + _this.userObjectives.sort(); + //_this.userObjectives.trigger('priorities-changed') + } - }); - return RankingObjectivesView; +}); +return RankingObjectivesView; } ); diff --git a/repair/js/visualizations/flowmap.js b/repair/js/visualizations/flowmap.js index 6fe9cb8c4..28d5b81b1 100644 --- a/repair/js/visualizations/flowmap.js +++ b/repair/js/visualizations/flowmap.js @@ -173,7 +173,7 @@ define([ totalValues.push(totalValue) }) this.maxFlowValue = Math.max(...totalValues); - this.minFlowValue = Math.min(...totalValues); + //this.minFlowValue = Math.min(...totalValues); } draw() { @@ -240,20 +240,29 @@ define([ }; } } + var options = { + xshift: xshift, + yshift: yshift, + animate: _this.animate, + dash: dash, + curve: curve + }; + var coords = [ + {x: sourceCoords[0], y: sourceCoords[1]}, + {x: targetCoords[0], y: targetCoords[1]} + ]; var path = _this.drawPath( - [ - {x: sourceCoords[0], y: sourceCoords[1]}, - {x: targetCoords[0], y: targetCoords[1]} - ], - flow.label, flow.color, strokeWidth, - { - xshift: xshift, - yshift: yshift, - animate: _this.animate, - dash: dash, - curve: curve - } + coords, flow.label, flow.color, strokeWidth, options ); + // workaround for mouseover very thin lines + // put invisible line on top (with mouseover) + if (!_this.animate && strokeWidth < 7) { + options.opacity = 0; + var bufferedPath = _this.drawPath( + coords, flow.label, flow.color, 7, options + ); + } + xshift -= shiftStep; yshift += shiftStep; }); @@ -426,12 +435,13 @@ define([ + " " + (target.x + controls[2]) + "," + (target.y + controls[3]) + " " + target.x + "," + target.y; }; + var opacity = (options.opacity != null) ? options.opacity : 0.5; var path = this.g.append("path") .attr('d', bezier(points)) .attr("stroke-width", strokeWidth) .attr("stroke", color) .attr("fill", 'none') - .attr("stroke-opacity", 0.5) + .attr("stroke-opacity", opacity) .attr("stroke-linecap", (!options.animate || (options.dash && options.dash.rounded)) ? "round": "unset") .style("pointer-events", (options.animate && (options.dash && options.dash.rounded)) ? 'none' : 'stroke') .on("mouseover", function () { @@ -450,7 +460,7 @@ define([ _this.tooltip.transition() .duration(500) .style("opacity", 0) - path.attr("stroke-opacity", 0.5) + path.attr("stroke-opacity", opacity) }) .classed('flow', true) .classed('animated', options.animate); diff --git a/repair/js/visualizations/map.js b/repair/js/visualizations/map.js index abb997246..e573a5b40 100644 --- a/repair/js/visualizations/map.js +++ b/repair/js/visualizations/map.js @@ -208,14 +208,19 @@ define([ } function labelStyle(feature, resolution) { + var fontSize = options.labelFontSize || '15px'; + var text = feature.get('label'); + if (_this.labelZoom && _this.map.getView().getZoom() < _this.labelZoom) + text = ''; return new ol.style.Text({ - font: '15px Open Sans,sans-serif', + font: fontSize + ' Open Sans,sans-serif', fill: new ol.style.Fill({ color: options.labelColor || '#4253f4' }), stroke: new ol.style.Stroke({ color: options.labelOutline || 'white', width: 3 }), - text: feature.get('label'), - overflow: false + text: text, + overflow: false, + offsetY: options.labelOffset || 0 }) } @@ -427,6 +432,14 @@ define([ return layer.getSource().getFeatures(); } + addFeatures(layername, features){ + var layer = this.layers[layername], + source = layer.getSource(); + features.forEach(function(feature){ + source.addFeature(feature); + }) + } + getFeature(layername, id){ var features = this.getFeatures(layername); for (var i = 0; i < features.length; i++){ @@ -820,6 +833,9 @@ define([ layer.getSource().addFeature(geojsonFormat.readFeature(intersection)); } }); + if (options.onDrawEnd){ + options.onDrawEnd(features); + } }); } diff --git a/repair/js/visualizations/sankey.js b/repair/js/visualizations/sankey.js index b83662102..ab52f7236 100644 --- a/repair/js/visualizations/sankey.js +++ b/repair/js/visualizations/sankey.js @@ -39,11 +39,18 @@ class Sankey{ .size([this.width * this.stretchFactor, this.height]) .align(alignment); this.selectable = options.selectable; + this.forceSignum = options.forceSignum; this.gradient = options.gradient; + this.selectOnDoubleClick = options.selectOnDoubleClick || false; } format(d) { - return d.toLocaleString(this.language); + var formatted = d.toLocaleString(this.language); + if (this.forceSignum){ + if (d > 0) formatted = '+' + formatted; + if (d == 0) formatted = '+-0'; + } + return formatted; } align(alignment){ @@ -166,14 +173,15 @@ class Sankey{ var inUnits, outUnits; for (var i = 0; i < d.targetLinks.length; i++) { var link = d.targetLinks[i]; - inSum += link.amount || link.value; + inSum += parseInt(link.amount || link.value); if (!inUnits) inUnits = link.units; // in fact take first occuring unit, ToDo: can there be different units in the future? } for (var i = 0; i < d.sourceLinks.length; i++) { var link = d.sourceLinks[i]; - outSum += link.amount || link.value; + outSum += parseInt(link.amount || link.value); if (!outUnits) outUnits = link.units; } + var ins = "in: " + _this.format(inSum) + " " + (inUnits || ""), out = "out: " + _this.format(outSum) + " " + (outUnits || ""); var text = (d.text) ? d.text + '
': ''; @@ -208,6 +216,7 @@ class Sankey{ .attr("width", this.width * this.stretchFactor) .attr("height", this.height) .call(this.zoom) + .on("dblclick.zoom", null) .call(tipLinks) .call(tipNodes) @@ -249,6 +258,8 @@ class Sankey{ return d.color || d.source.color || '#000'; } + var selectEvent = (this.selectOnDoubleClick) ? 'dblclick': 'click'; + var link = g.append("g").attr("class", "link-container") .selectAll(".link") .data(data.links) @@ -271,7 +282,7 @@ class Sankey{ }) .on('mouseover', function(d) { tipLinks.show(d, this); }) .on('mouseout', function(d) { tipLinks.hide(d, this); }) - .on('click', function(d){ + .on(selectEvent, function(d){ if (_this.selectable){ var link = d3.select(this), selected = link.classed("selected"); diff --git a/repair/locale/hu/LC_MESSAGES/django.po b/repair/locale/hu/LC_MESSAGES/django.po index 6942600aa..14fd77316 100644 --- a/repair/locale/hu/LC_MESSAGES/django.po +++ b/repair/locale/hu/LC_MESSAGES/django.po @@ -3,311 +3,1273 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-10-15 14:25+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"POT-Creation-Date: 2019-05-29 10:52+0200\n" +"PO-Revision-Date: 2019-07-19 11:55+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Last-Translator: \n" +"Language-Team: \n" +"X-Generator: Poedit 2.2.3\n" +"Language: hu_HU\n" + +#: .\docs\views_baseview.js.html:181 .\repair\js\views\common\baseview.js:176 +msgid "Warning" +msgstr "Figyelmeztetés" + +#: .\docs\views_baseview.js.html:197 .\repair\js\views\common\baseview.js:299 +msgid "The server responded with: " +msgstr "A kiszolgáló válaszol " + +#: .\docs\views_baseview.js.html:198 +msgid "Server does not respond." +msgstr "A kiszolgáló nem válaszol." + +#: .\docs\views_baseview.js.html:199 +#: .\docs\views_data-entry_edit-node.js.html:646 +#: .\repair\js\views\data-entry\bulk-upload.js:399 +#: .\repair\js\views\data-entry\edit-node.js:697 .\repair\js\welcome.js:119 +msgid "Error" +msgstr "Hiba" + +#: .\docs\views_changes_solutions.js.html:107 +msgid "Solution" +msgstr "Megoldás" + +#: .\docs\views_changes_solutions.js.html:168 +#: .\docs\views_changes_solutions.js.html:169 +#: .\docs\views_data-entry_edit-actor.js.html:808 +#: .\docs\views_data-entry_edit-actor.js.html:810 +#: .\docs\views_status-quo_evaluation.js.html:111 +#: .\docs\views_status-quo_evaluation.js.html:112 +#: .\docs\views_study-area_maps.js.html:280 +#: .\docs\views_study-area_maps.js.html:281 +#: .\docs\views_study-area_setup-maps.js.html:145 +#: .\docs\views_study-area_setup-maps.js.html:146 +#: .\repair\js\views\data-entry\edit-actor.js:825 +#: .\repair\js\views\strategy\setup-solutions-logic.js:427 +msgid "Focus area" +msgstr "Fókuszterület" + +#: .\docs\views_data-entry_actors.js.html:132 +#: .\docs\views_data-entry_edit-node.js.html:797 +#: .\repair\js\views\data-entry\edit-node.js:868 +msgid "Search" +msgstr "Keresés" + +#: .\docs\views_data-entry_actors.js.html:252 +#: .\repair\js\views\data-entry\actors-flows.js:389 +msgid "Add Actor" +msgstr "Szereplő hozzáadása" + +#: .\docs\views_data-entry_actors.js.html:264 +#: .\repair\js\views\data-entry\actors-flows.js:399 +msgid "Do you really want to delete the actor" +msgstr "Biztosan törölni akarja a szereplőt" + +#: .\docs\views_data-entry_actors.js.html:289 +#: .\docs\views_data-entry_materials.js.html:308 +#: .\docs\views_study-area_charts.js.html:193 +#: .\docs\views_study-area_setup-maps.js.html:448 +#: .\repair\js\views\common\baseview.js:340 +#: .\repair\js\views\data-entry\edit-node.js:821 +#: .\repair\js\views\status-quo\objectives.js:249 +#: .\repair\js\views\status-quo\objectives.js:295 +#: .\repair\js\views\status-quo\objectives.js:325 +#: .\repair\js\views\study-area\stakeholders.js:230 +#: .\repair\js\views\study-area\stakeholders.js:263 +msgid "Name" +msgstr "Név" + +#: .\docs\views_data-entry_edit-actor.js.html:496 +#: .\repair\js\views\data-entry\edit-actor.js:490 +msgid "select an area" +msgstr "Válasszon területet " + +#: .\docs\views_data-entry_edit-node.js.html:268 +#: .\docs\views_flowsankey.js.html:190 .\docs\views_flowsankey.js.html:206 +#: .\repair\js\views\common\flowsankey.js:207 +#: .\repair\js\views\common\flowsankey.js:258 +#: .\repair\js\views\data-entry\edit-node.js:258 +#: .\repair\js\views\strategy\setup-question.js:67 +msgid "t/year" +msgstr "t/év" + +#: .\docs\views_data-entry_edit-node.js.html:306 +#: .\repair\js\views\common\flowsankeymap.js:674 +#: .\repair\js\views\data-entry\edit-node.js:328 +msgid "Waste" +msgstr "Hulladék" + +#: .\docs\views_data-entry_edit-node.js.html:309 +#: .\repair\js\views\common\flowsankeymap.js:674 +#: .\repair\js\views\data-entry\edit-node.js:331 +msgid "Product" +msgstr "Termék" + +#: .\docs\views_data-entry_edit-node.js.html:323 +#: .\repair\js\views\data-entry\edit-node.js:349 +msgid "Composition" +msgstr "Összetétel" + +#: .\docs\views_data-entry_edit-node.js.html:439 +msgid "edit datasource" +msgstr "adatforrás szerkesztése" + +#: .\docs\views_data-entry_edit-node.js.html:495 +#: .\docs\views_data-entry_edit-node.js.html:655 +#: .\repair\js\views\data-entry\edit-node.js:534 +#: .\repair\js\views\data-entry\edit-node.js:706 +msgid "custom" +msgstr "általános" + +#: .\docs\views_data-entry_edit-node.js.html:573 +#: .\repair\js\views\data-entry\edit-node.js:630 +msgid "remove fraction" +msgstr "a frakció eltávolítása" + +#: .\docs\views_data-entry_edit-node.js.html:626 +#: .\repair\js\views\data-entry\edit-node.js:683 +msgid "The fractions have to sum up to 100!" +msgstr "A frakciók összege 100!" + +#: .\docs\views_data-entry_edit-node.js.html:626 +#: .\repair\js\views\data-entry\edit-node.js:683 +msgid "current sum" +msgstr "jelenlegi érték" + +#: .\docs\views_data-entry_edit-node.js.html:634 +#: .\repair\js\views\data-entry\edit-node.js:690 +msgid "All materials have to be set!" +msgstr "Minden anyagot be kell állítani!" + +#: .\docs\views_data-entry_edit-node.js.html:638 +msgid "Multiple fractions with the same material are not allowed!" +msgstr "Több frakció egyazon anyaggal nem megengedett!" + +#: .\docs\views_data-entry_materials.js.html:215 +#: .\repair\js\views\data-entry\materials.js:207 +msgid "Add Material" +msgstr "Anyag hozzáadása" + +#: .\docs\views_data-entry_materials.js.html:244 +#: .\repair\js\views\data-entry\materials.js:248 +msgid "Edit Material" +msgstr "Anyag szerkesztése" + +#: .\docs\views_data-entry_materials.js.html:262 +#: .\repair\js\views\data-entry\materials.js:278 +msgid "" +"Do you really want to delete the selected material and all of its children " +"from the database?" +msgstr "" +"Biztosan törölni szeretné a kiválasztott anyagot és az összes gyermekét az " +"adatbázisból?" + +#: .\docs\views_status-quo_flows.js.html:163 +#: .\repair\js\views\common\filter-flows.js:380 +#: .\repair\js\views\status-quo\edit-indicator-flow.js:251 +msgid "All" +msgstr "Mind" + +#: .\docs\views_study-area_charts.js.html:157 +#: .\docs\views_study-area_setup-maps.js.html:272 +#: .\repair\js\views\study-area\charts.js:295 +#: .\repair\js\views\study-area\setup-maps.js:223 +msgid "Add Category" +msgstr "Kategória hozzáadása" + +#: .\docs\views_study-area_setup-maps.js.html:115 +#: .\repair\js\views\status-quo\objectives.js:106 +msgid "Remove" +msgstr "Törlés" -#: .\repair\apps\statusquo\views.py:20 .\repair\apps\studyarea\views.py:28 -msgid "Plotly graph" +#: .\docs\views_study-area_setup-maps.js.html:331 +#: .\repair\js\views\study-area\setup-maps.js:292 +msgid "Do you really want to delete the selected layer?" +msgstr "Biztosan eltávolítja a kiválasztott réteget?" + +#: .\docs\views_study-area_setup-maps.js.html:332 +msgid "Do you really want to delete the selected category?" +msgstr "Biztosan eltávoítja a kiválasztott kategóriát?" + +#: .\docs\views_study-area_setup-maps.js.html:394 +#: .\repair\js\views\study-area\charts.js:417 +#: .\repair\js\views\study-area\setup-maps.js:351 +msgid "Edit Name" +msgstr "Név szerkesztése" + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:25 +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid " " msgstr "" -#: .\repair\apps\studyarea\views.py:40 -msgid "Plotly Histogram" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:25 +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "." msgstr "" -#: .\repair\settings.py:165 -msgid "English" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "*" msgstr "" -#: .\repair\settings.py:166 -msgid "German" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "import" +msgstr "importálás" + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "{}" msgstr "" -#: .\repair\templates\admin\activity-groups-edit.html:6 -msgid "Edit Activity Group" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid ", " msgstr "" -#: .\repair\templates\admin\activity-groups-edit.html:53 -msgid "Save & Exit" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "this" +msgstr "ez" + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "super" msgstr "" -#: .\repair\templates\admin\index.html:18 -#: .\repair\templates\changes\casestudy.html:9 -msgid "Case Study" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "null" +msgstr "hiányzó érték" + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid ",\n" msgstr "" -#: .\repair\templates\admin\index.html:27 -msgid "Flow" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid " = " msgstr "" -#: .\repair\templates\admin\index.html:36 -msgid "Data Entered by" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "\n" msgstr "" -#: .\repair\templates\admin\index.html:65 -msgid "Enter Data" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "debugger;" +msgstr "hibakereső " + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid ":" msgstr "" -#: .\repair\templates\admin\index.html:70 -msgid "View Data" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "empty" +msgstr "üres" + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "any" +msgstr "bármely" + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "mixed" +msgstr "vegyes" + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "boolean" +msgstr "logikai" + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "interface " +msgstr "interfész " + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid " & " msgstr "" -#: .\repair\templates\admin\index.html:77 -msgid "Balance & Verify Data" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "number" +msgstr "szám" + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "string" +msgstr "korlátozás" + +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "typeof " msgstr "" -#: .\repair\templates\admin\index.html:84 -msgid "Balance Data" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid " | " msgstr "" -#: .\repair\templates\admin\index.html:89 -msgid "Verify Data" +#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 +msgid "void" +msgstr "üres, hiányos" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "using url" msgstr "" -#: .\repair\templates\admin\index.html:94 -msgid "Please choose the level for which you wish to enter data" +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "_receiveInfo" msgstr "" -#: .\repair\templates\base.html:35 -msgid "Study Area" +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "info" msgstr "" -#: .\repair\templates\base.html:36 -msgid "Status Quo" +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid " enabled transports" +msgstr " engedélyezett szállítás" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "attempt" +msgstr "kísérlet" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "waiting for body" msgstr "" -#: .\repair\templates\base.html:37 -msgid "Changes" +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "using timeout" +msgstr "időtúllépés használata" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "transport url" +msgstr "szállítási url" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "_transportTimeout" +msgstr "szállításiIdőtúllépés " + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "_transportMessage" +msgstr "szállításiÜzenet" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "heartbeat" +msgstr "szívverés" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "bad json" +msgstr "rossz json" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "message" +msgstr "üzenet" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "empty payload" +msgstr "üres hasznos teher " + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "_transportClose" +msgstr "szállításBezárás " + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "_open" +msgstr "_megnyitás" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "connected" +msgstr "kapcsolódva" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "_close" +msgstr "_bezárás" + +#: .\node_modules\webpack-dev-server\client\index.bundle.js:1 +msgid "disconnected" +msgstr "szétkapcsolás" + +#: .\repair\apps\admin.py:51 +msgid "The requested admin page is " +msgstr "A kért admin oldal " + +#: .\repair\apps\asmfa\serializers\bulkcreate.py:136 +msgid "Flows from an actor to itself are not allowed." +msgstr "A aktor átal saját magához való áramoltatás nem megengedett." + +#: .\repair\apps\asmfa\serializers\bulkcreate.py:263 +msgid "fractions per composition have to sum up to 1.0 with " +msgstr "afrakció / kompozíciók összege legfeljebb 1,0 " + +#: .\repair\apps\asmfa\serializers\keyflows.py:41 +msgid "Select the Casestudies the Keyflow is used in" +msgstr "Válassza ki az esettanulmányt amelyben az Anyagáramot használni kívánja" + +#: .\repair\apps\asmfa\serializers\locations.py:58 +msgid "Actor <{}> already has an administrative location " +msgstr "A szereplő már rendelkezik adminisztratív hellyel " + +#: .\repair\apps\asmfa\serializers\nodes.py:66 +msgid "This field is required." +msgstr "kötelezenően kitöltendő mező " + +#: .\repair\apps\asmfa\serializers\nodes.py:67 +msgid "Invalid Actor ID - Object does not exist." +msgstr "Érvénytelen ID -az objektum nem létezik." + +#: .\repair\apps\asmfa\serializers\nodes.py:68 +msgid "This field may not be null." +msgstr "A mező értéke nem lehet nulla." + +#: .\repair\apps\asmfa\serializers\nodes.py:177 +msgid "Enter a valid URL (or leave it blank)." +msgstr "Adjon meg egy érvényes URL-t (vagy hagyja üresen)." + +#: .\repair\apps\asmfa\views\flowfilter.py:172 +#: .\repair\apps\statusquo\views\indicators.py:425 +msgid "calculation is not done yet" +msgstr "számítás még nincs kész" + +#: .\repair\apps\asmfa\views\flowfilter.py:175 +#: .\repair\apps\statusquo\views\indicators.py:428 +msgid "calculation is still in process" +msgstr "A számítás folyamatban van. " + +#: .\repair\apps\asmfa\views\keyflows.py:243 +msgid "This material is a default material " +msgstr "Alapértelmezett anyag " + +#: .\repair\apps\changes\models\solutions.py:20 +msgid "Enter only floats separated by commas." +msgstr "Vesszővel válassza el a bevitt tételeket." + +#: .\repair\apps\changes\serializers\strategies.py:105 +msgid "not calculated yet" +msgstr "számítás alatt" + +#: .\repair\apps\changes\serializers\strategies.py:109 +msgid "calculation started" +msgstr "A szímátás elkezdődött. " + +#: .\repair\apps\changes\serializers\strategies.py:111 +#: .\repair\apps\changes\serializers\strategies.py:118 +msgid "(server time)" +msgstr "(szerveridő)" + +#: .\repair\apps\changes\serializers\strategies.py:112 +msgid "elapsed" +msgstr "lejárt az idő" + +#: .\repair\apps\changes\serializers\strategies.py:116 +msgid "calculation finished" +msgstr "a számítás elkészült" + +#: .\repair\apps\changes\views\strategies.py:75 +msgid "The base data is not set up. " +msgstr "Az alapadatok nincsenek beállítva. " + +#: .\repair\apps\login\serializers\bases.py:109 +msgid "User {} has no permission to access casestudy {}" +msgstr "A felhasználónak nincs hozzáférése az esettanulmányhoz. " + +#: .\repair\apps\login\serializers\users.py:40 +msgid "Select the Casestudies the user works on" +msgstr "Az esettanulmány kiválasztása amelyen a felhasználó dolgozik" + +#: .\repair\apps\login\views\users.py:124 +#: .\repair\apps\login\views\users.py:171 +#: .\repair\apps\login\views\users.py:195 +#: .\repair\apps\login\views\users.py:200 +msgid "Unauthorized" +msgstr "nem engedélyezett " + +#: .\repair\apps\statusquo\views\indicators.py:243 +msgid "SUM aggregation Flow A" +msgstr "SUM aggregáció Flow A" + +#: .\repair\apps\statusquo\views\indicators.py:244 +msgid "Flow A" +msgstr "A áram" + +#: .\repair\apps\statusquo\views\indicators.py:245 +msgid "t / year" +msgstr "t / év" + +#: .\repair\apps\statusquo\views\indicators.py:261 +msgid "SUM aggregation Flow A / SUM aggregation Flow B" +msgstr "SUM \"A\" ggregációs áram / SUM \"B\" aggregációs áram " + +#: .\repair\apps\statusquo\views\indicators.py:262 +msgid "(Flow A / Flow B) * 100" +msgstr "(A áram / B áram ) * 110" + +#: .\repair\apps\statusquo\views\indicators.py:298 +msgid "SUM aggregation Flow A / Inhabitants in Area" +msgstr "SUM aggregáció Flow A / lakosok a területen" + +#: .\repair\apps\statusquo\views\indicators.py:299 +msgid "Flow A / Inhabitants" +msgstr "A áram / lakosok" + +#: .\repair\apps\statusquo\views\indicators.py:300 +msgid "kg / inhabitant and year" +msgstr "kg / lakos és év" + +#: .\repair\apps\statusquo\views\indicators.py:341 +msgid "SUM aggregation Flow A / geometrical area in hectar" +msgstr "SUM aggregáció Áramlás A / geometriai terület hektárban" + +#: .\repair\apps\statusquo\views\indicators.py:342 +msgid "Flow A / Area (ha)" +msgstr "A áram / terület (ha)" + +#: .\repair\apps\statusquo\views\indicators.py:343 +msgid "t / hectar and year" +msgstr "t / hektár és év" + +#: .\repair\apps\studyarea\serializers\areas.py:135 +msgid "you may only pass parent_area_id OR " +msgstr "csak a szülőterületet érheti el" + +#: .\repair\apps\studyarea\serializers\areas.py:148 +msgid "parent_level is required when relating to " +msgstr "a szülő_szintre akkor van szükség, ha az" + +#: .\repair\apps\utils\serializers.py:417 +msgid "unsupported filetype" +msgstr "nem támogatott fájltípus " + +#: .\repair\apps\utils\serializers.py:422 +msgid "wrong file-encoding ({} used)" +msgstr "rossz fájlkódolás ({} használva)" + +#: .\repair\apps\utils\serializers.py:457 +msgid "Index column(s) missing: {}" +msgstr "Az index oszlop (ok) hiányzik: {}" + +#: .\repair\apps\utils\serializers.py:465 +msgid "Index \"{}\" has to be unique!" +msgstr "Index {{} \" csak önmagában állhat!" + +#: .\repair\apps\utils\serializers.py:468 +msgid "The combination of indices \"{}\" have to be unique!" +msgstr "A \"{}\" indexek kombinációjának egyedinek kell lennie!" + +#: .\repair\apps\utils\serializers.py:470 +msgid "Duplicates found: {}" +msgstr "A másolatok megtalálhatók: {}" + +#: .\repair\apps\utils\serializers.py:572 +msgid "invalid geometry" +msgstr "érvénytelen geometria " + +#: .\repair\apps\utils\serializers.py:575 +msgid "Invalid geometries" +msgstr "Érvénytelen geometriák " + +#: .\repair\apps\utils\serializers.py:588 +msgid "Integer expected: number without decimals" msgstr "" -#: .\repair\templates\base.html:38 -msgid "Impacts" +#: .\repair\apps\utils\serializers.py:592 +msgid "Float expected: number with or without " msgstr "" -#: .\repair\templates\base.html:39 -msgid "Decisions" +#: .\repair\apps\utils\serializers.py:597 +msgid "Boolean expected (\"true\" or \"false\")" msgstr "" -#: .\repair\templates\base.html:40 -msgid "Admin Area" +#: .\repair\apps\utils\serializers.py:601 +msgid "Number format errors" msgstr "" -#: .\repair\templates\changes\casestudy.html:11 -msgid "Users in Casestudy:" +#: .\repair\apps\utils\serializers.py:628 +msgid "Column(s) missing: {}" msgstr "" -#: .\repair\templates\changes\casestudy.html:21 -msgid "Catalogue of Solutions" +#: .\repair\apps\utils\serializers.py:652 +msgid "{c} - related models {m} not found" msgstr "" -#: .\repair\templates\changes\implementation.html:8 -msgid "Implementation" +#: .\repair\apps\utils\serializers.py:655 +msgid "relation not found" msgstr "" -#: .\repair\templates\changes\implementation.html:8 -msgid "coordinated by" +#: .\repair\apps\utils\views.py:303 +msgid "Referencing Object(s)" msgstr "" -#: .\repair\templates\changes\implementation.html:9 -#: .\repair\templates\changes\solution.html:9 -msgid "Created by" +#: .\repair\js\conclusions.js:63 +msgid "There are no specified users! Please go to setup mode." msgstr "" -#: .\repair\templates\changes\implementation.html:12 -msgid "consists of the following solutions" +#: .\repair\js\strategy.js:96 +msgid "Calculation started. Please wait till it is finished (check Status)." msgstr "" -#: .\repair\templates\changes\implementation.html:19 -msgid "No solutions in" +#: .\repair\js\strategy.js:140 +msgid "Graph was successfully build." msgstr "" -#: .\repair\templates\changes\implementation.html:23 -msgid "Implementation is part of the following strategies:" +#: .\repair\js\targets.js:52 +msgid "" +"Flow targets can't be set for general objectives.

Please select a " +"keyflow inside the side-menu." msgstr "" -#: .\repair\templates\changes\implementation.html:32 -msgid "Implementation needs the following stakeholders" +#: .\repair\js\views\common\baseview.js:278 +msgid "Info" msgstr "" -#: .\repair\templates\changes\index.html:10 -msgid "Solutions" +#: .\repair\js\views\common\baseview.js:300 +msgid "Error " msgstr "" -#: .\repair\templates\changes\index.html:11 -msgid "Implementations" +#: .\repair\js\views\common\filter-flows.js:392 +#: .\repair\js\views\data-entry\materials.js:77 +#: .\repair\js\views\status-quo\edit-indicator-flow.js:263 +msgid "flows" msgstr "" -#: .\repair\templates\changes\index.html:12 -msgid "Strategies" +#: .\repair\js\views\common\filter-flows.js:399 +msgid "too many to display" msgstr "" -#: .\repair\templates\changes\index.html:24 -msgid "Users" +#: .\repair\js\views\common\filter-flows.js:485 +#: .\repair\js\views\status-quo\edit-indicator-flow.js:305 +msgid "All materials" msgstr "" -#: .\repair\templates\changes\index.html:35 -msgid "Casestudies" +#: .\repair\js\views\common\flowsankey.js:92 +msgid "No flow data found for applied filters." msgstr "" -#: .\repair\templates\changes\index.html:42 -msgid "No casestudies are available." +#: .\repair\js\views\common\flowsankey.js:174 +#: .\repair\js\views\common\flowsankeymap.js:559 +msgid "Actor" msgstr "" -#: .\repair\templates\changes\solution.html:8 -msgid "Solution" +#: .\repair\js\views\common\flowsankey.js:210 +msgid "avoidable" msgstr "" -#: .\repair\templates\changes\solution.html:9 -msgid "User" +#: .\repair\js\views\common\flowsankey.js:222 +#: .\repair\js\views\common\flowsankeymap.js:675 +msgid "Process" msgstr "" -#: .\repair\templates\changes\solution.html:10 -msgid "Description" +#: .\repair\js\views\common\flowsankey.js:308 +#: .\repair\js\views\strategy\setup-solution-part.js:107 +#: .\repair\js\views\strategy\setup-solution-part.js:216 +msgid "origin" msgstr "" -#: .\repair\templates\changes\solution.html:11 -msgid "One unit equals" +#: .\repair\js\views\common\flowsankey.js:308 +msgid "origin_code" msgstr "" -#: .\repair\templates\changes\solution.html:14 -msgid "The solution has the following ratios per unit defined" +#: .\repair\js\views\common\flowsankey.js:309 +#: .\repair\js\views\strategy\setup-solution-part.js:107 +#: .\repair\js\views\strategy\setup-solution-part.js:216 +msgid "destination" +msgstr "desztináció" + +#: .\repair\js\views\common\flowsankey.js:309 +msgid "destination_code" +msgstr "desztináció kód" + +#: .\repair\js\views\common\flowsankey.js:310 +msgid "amount" +msgstr "összeg" + +#: .\repair\js\views\common\flowsankey.js:310 +msgid "composition" +msgstr "összetétel" + +#: .\repair\js\views\common\flowsankey.js:318 +#: .\repair\js\views\common\flowsankeymap.js:727 +msgid "Stock" msgstr "" -#: .\repair\templates\changes\solution.html:24 -msgid "" -"An Implementations has to define the following quantities for this solution" +#: .\repair\js\views\common\flowsankeymap.js:131 +msgid "Display materials" msgstr "" -#: .\repair\templates\changes\solution.html:33 -msgid "Solution is part of the following implementations" +#: .\repair\js\views\common\flowsankeymap.js:132 +msgid "Animate flows" msgstr "" -#: .\repair\templates\changes\user.html:10 -msgid "In CaseStudies:" +#: .\repair\js\views\common\flowsankeymap.js:133 +msgid "Cluster locations" msgstr "" -#: .\repair\templates\changes\user.html:17 -msgid "No Casestudy assigned to." +#: .\repair\js\views\common\flowsankeymap.js:134 +msgid "Show actors" +msgstr "szereplők megmutatása" + +#: .\repair\js\views\common\flowsankeymap.js:135 +msgid "Show flows" +msgstr "áramok megmutatása" + +#: .\repair\js\views\common\flowsankeymap.js:136 +msgid "Show stocks" msgstr "" -#: .\repair\templates\changes\user_in_casestudy.html:10 -msgid "Solutions created:" +#: .\repair\js\views\common\flowsankeymap.js:522 +msgid "actors" +msgstr "szereplők" + +#: .\repair\js\views\common\flowsankeymap.js:563 +msgid "Actor referenced by flow, but missing a location:" +msgstr "Szereplő amelyre az áramlás vonatkozik, de hiányó hely:" + +#: .\repair\js\views\conclusions\flow-targets.js:112 +msgid "Objectives for key flow " msgstr "" -#: .\repair\templates\changes\user_in_casestudy.html:17 -msgid "No solutions defined by." +#: .\repair\js\views\conclusions\flow-targets.js:158 +msgid "Indicators used as target setting in the key flow " msgstr "" -#: .\repair\templates\changes\user_in_casestudy.html:21 -msgid "Implementations created:" +#: .\repair\js\views\conclusions\manage-notepad.js:145 +#: .\repair\js\views\status-quo\objectives.js:309 +msgid "Edit" +msgstr "szerkesztés" + +#: .\repair\js\views\conclusions\manage-notepad.js:163 +msgid "Do you want to delete the item?" +msgstr "Törölni akarja az elemet?" + +#: .\repair\js\views\conclusions\manage-notepad.js:173 +#: .\repair\js\views\status-quo\objectives.js:339 +#: .\repair\js\views\strategy\setup-solutions-logic.js:295 +msgid "Do you want to delete the selected item?" +msgstr "Szeretné törölni a kiválasztott elemet?" + +#: .\repair\js\views\conclusions\objectives.js:66 +msgid "Objectives for keyflow " msgstr "" -#: .\repair\templates\changes\user_in_casestudy.html:28 -msgid "No implementations defined by." +#: .\repair\js\views\conclusions\objectives.js:68 +msgid "General objectives" +msgstr "Általéános célok" + +#: .\repair\js\views\data-entry\bulk-upload.js:55 +#: .\repair\js\views\data-entry\edit-node.js:825 +#: .\repair\js\views\data-entry\edit-node.js:831 +msgid "Activity Group" +msgstr "tevékenységcsoport" + +#: .\repair\js\views\data-entry\bulk-upload.js:56 +msgid "Activities" +msgstr "Tevékenységek" + +#: .\repair\js\views\data-entry\bulk-upload.js:57 +msgid "Actors" +msgstr "Szereplők" + +#: .\repair\js\views\data-entry\bulk-upload.js:58 +msgid "Actor Locations" +msgstr "Szereplő helyzete" + +#: .\repair\js\views\data-entry\bulk-upload.js:59 +msgid "Materials" +msgstr "Anyagok" + +#: .\repair\js\views\data-entry\bulk-upload.js:60 +msgid "Products" +msgstr "Termékek" + +#: .\repair\js\views\data-entry\bulk-upload.js:61 +msgid "Wastes" +msgstr "Hulladékok" + +#: .\repair\js\views\data-entry\bulk-upload.js:62 +msgid "Actor to Actor Flows" +msgstr "Szereplőtől szereplőhöz áramlás" + +#: .\repair\js\views\data-entry\bulk-upload.js:63 +msgid "Actor Stocks" msgstr "" -#: .\repair\templates\changes\user_in_casestudy.html:32 -msgid "In other CaseStudies:" +#: .\repair\js\views\data-entry\bulk-upload.js:66 +msgid "Area Levels" msgstr "" -#: .\repair\templates\changes\user_in_casestudy.html:39 -msgid "No other Casestudy assigned to" +#: .\repair\js\views\data-entry\bulk-upload.js:67 +msgid "Areas" +msgstr "Területek" + +#: .\repair\js\views\data-entry\bulk-upload.js:68 +msgid "Publications" +msgstr "Publikációk" + +#: .\repair\js\views\data-entry\bulk-upload.js:106 +msgid "Upload bibtex files" msgstr "" -#: .\repair\templates\decisions\index.html:10 -#: .\repair\templates\impacts\index.html:10 -#: .\repair\templates\statusquo\index.html:10 -msgid "Flows" +#: .\repair\js\views\data-entry\bulk-upload.js:108 +msgid "here" +msgstr "Itt" + +#: .\repair\js\views\data-entry\bulk-upload.js:137 +msgid "Focusarea updated" msgstr "" -#: .\repair\templates\impacts\index.html:11 -#: .\repair\templates\statusquo\index.html:11 -msgid "Evaluation" +#: .\repair\js\views\data-entry\bulk-upload.js:141 +msgid "Focusarea update failed" msgstr "" -#: .\repair\templates\statusquo\evaluation.html:7 -msgid "All indicators" +#: .\repair\js\views\data-entry\bulk-upload.js:157 +msgid "Casestudy Region updated" +msgstr "Esettanulmány régió frissült " + +#: .\repair\js\views\data-entry\bulk-upload.js:161 +msgid "Casestudy Region update failed" +msgstr "Esettanulmány régió frissítése sikertelen" + +#: .\repair\js\views\data-entry\bulk-upload.js:188 +msgid "Do you really want to delete the keyflow and ALL of its data?" msgstr "" -#: .\repair\templates\statusquo\evaluation.html:11 -msgid "By endpoint" +#: .\repair\js\views\data-entry\bulk-upload.js:191 +msgid "Are you sure?" msgstr "" -#: .\repair\templates\statusquo\evaluation.html:14 -msgid "By midpoint" +#: .\repair\js\views\data-entry\bulk-upload.js:213 +msgid "Removing data" msgstr "" -#: .\repair\templates\statusquo\evaluation.html:26 -msgid "Selected indicator" +#: .\repair\js\views\data-entry\bulk-upload.js:218 +msgid "Nothing to remove" msgstr "" -#: .\repair\templates\statusquo\flows.html:7 -msgid "Focus Area Only" +#: .\repair\js\views\data-entry\bulk-upload.js:227 +msgid "entries removed" msgstr "" -#: .\repair\templates\statusquo\flows.html:12 -msgid "Organic waste (from households only)" +#: .\repair\js\views\data-entry\bulk-upload.js:228 +msgid "entries failed to remove" msgstr "" -#: .\repair\templates\statusquo\flows.html:18 -msgid "Material and waste flows
by space" +#: .\repair\js\views\data-entry\bulk-upload.js:265 +msgid "Successfully deleted" msgstr "" -#: .\repair\templates\statusquo\flows.html:24 -msgid "Material and waste flows
by activity" +#: .\repair\js\views\data-entry\bulk-upload.js:293 +msgid "Force CASCADED deletion (delete referencing objects, even if protected)" msgstr "" -#: .\repair\templates\studyarea\basedata.html:7 -msgid "Text" +#: .\repair\js\views\data-entry\bulk-upload.js:299 +msgid "Do you really want to delete the existing data and the related data?" msgstr "" -#: .\repair\templates\studyarea\basedata.html:8 +#: .\repair\js\views\data-entry\bulk-upload.js:333 +msgid "No file selected to upload!" +msgstr "Nem választotta ki a feltöltendő fájlt" + +#: .\repair\js\views\data-entry\bulk-upload.js:348 +msgid "Uploading" +msgstr "töltés" + +#: .\repair\js\views\data-entry\bulk-upload.js:356 +msgid "Created models:" +msgstr "létrehozott modellek" + +#: .\repair\js\views\data-entry\bulk-upload.js:361 +msgid "Updated models:" +msgstr "frissített modellek" + +#: .\repair\js\views\data-entry\bulk-upload.js:371 +#: .\repair\js\views\status-quo\setup-flow-assessment.js:198 +#: .\repair\js\views\status-quo\setup-flows.js:125 +#: .\repair\js\views\strategy\setup-solutions-logic.js:445 +#: .\repair\js\views\strategy\setup-solutions.js:197 +#: .\repair\js\views\study-area\keyflows.js:116 +msgid "Success" +msgstr "siker" + +#: .\repair\js\views\data-entry\bulk-upload.js:436 msgid "" -"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy " -"eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam " -"voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet " -"clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit " -"amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam " -"nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed " -"diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet " -"clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." +"Warning: Uploading Area Levels removes all Areas in the casestudy as well. " +"Do you wish to continue?" msgstr "" -#: .\repair\templates\studyarea\basedata.html:22 -msgid "Map with Sankey" +#: .\repair\js\views\data-entry\bulk-upload.js:451 +msgid "Response" +msgstr "válasz" + +#: .\repair\js\views\data-entry\bulk-upload.js:489 +#: .\repair\js\views\data-entry\bulk-upload.js:497 +msgid "count" msgstr "" -#: .\repair\templates\studyarea\basedata.html:24 -msgid "Interaction Test" +#: .\repair\js\views\data-entry\bulk-upload.js:499 +msgid "defaults excluded" msgstr "" -#: .\repair\templates\studyarea\index.html:10 -msgid "Base data" +#: .\repair\js\views\data-entry\edit-node.js:292 +msgid "Lade..." msgstr "" -#: .\repair\templates\studyarea\index.html:11 -msgid "Stakeholders" +#: .\repair\js\views\data-entry\edit-node.js:414 +msgid "None" +msgstr "egyik sem" + +#: .\repair\js\views\data-entry\edit-node.js:604 +msgid "Select a material" +msgstr "válasszon anyagot " + +#: .\repair\js\views\data-entry\edit-node.js:826 +msgid "Nace Code" msgstr "" -#: .\repair\templates\studyarea\stakeholders.html:7 -msgid "Government" +#: .\repair\js\views\data-entry\edit-node.js:830 +msgid "Activity" +msgstr "tevékenység" + +#: .\repair\js\views\data-entry\edit-node.js:832 +msgid "City" +msgstr "város" + +#: .\repair\js\views\data-entry\edit-node.js:833 +msgid "Address" +msgstr "cím" + +#: .\repair\js\views\data-entry\materials.js:83 +msgid "read only" +msgstr "csak olvasásra" + +#: .\repair\js\views\data-entry\materials.js:120 +msgid "Material (directly used in flows / children in flows)" +msgstr "Anyag (közvetlenül áramlásokban / áramlásokban használt gyermekeknél)" + +#: .\repair\js\views\status-quo\edit-indicator-flow.js:271 +msgid "select an activity/group to display specific nodes" msgstr "" +"válasszon egy tevékenységet / csoportot, hogy megjelenítse az adott " +"csomópontokat" + +#: .\repair\js\views\status-quo\objectives.js:88 +msgid "General" +msgstr "általános" + +#: .\repair\js\views\status-quo\objectives.js:159 +#: .\repair\js\views\status-quo\objectives.js:238 +msgid "Challenge" +msgstr "kihívás" + +#: .\repair\js\views\status-quo\objectives.js:160 +#: .\repair\js\views\status-quo\objectives.js:284 +msgid "Aim" +msgstr "cél" + +#: .\repair\js\views\status-quo\objectives.js:245 +msgid "Add Challenge" +msgstr "kihívás hozzásadás" + +#: .\repair\js\views\status-quo\objectives.js:253 +#: .\repair\js\views\status-quo\objectives.js:299 +#: .\repair\js\views\status-quo\objectives.js:330 +#: .\repair\js\views\study-area\stakeholders.js:234 +#: .\repair\js\views\study-area\stakeholders.js:268 +msgid "Description" +msgstr "leírás" + +#: .\repair\js\views\status-quo\objectives.js:291 +msgid "Add Aim" +msgstr "Cél hozzáadása" + +#: .\repair\js\views\status-quo\setup-flow-assessment.js:198 +#: .\repair\js\views\status-quo\setup-flows.js:125 +#: .\repair\js\views\strategy\setup-solutions-logic.js:445 +#: .\repair\js\views\strategy\setup-solutions.js:197 +#: .\repair\js\views\study-area\keyflows.js:116 +msgid "Upload successful" +msgstr "feltöltés sikeres" + +#: .\repair\js\views\status-quo\setup-flow-assessment.js:240 +msgid "Do you want to delete the Indicator" +msgstr "Valóban törölni kívánja az indikátort? " + +#: .\repair\js\views\status-quo\setup-flows.js:168 +msgid "Do you want to delete the Filter" +msgstr "Valóban törölni kívánja a szűrőt?" + +#: .\repair\js\views\status-quo\workshop-flow-assessment.js:496 +msgid "Case Study
Region" +msgstr "Esettanulmány
Régió" + +#: .\repair\js\views\status-quo\workshop-flow-assessment.js:496 +msgid "Focus
Area" +msgstr "" + +#: .\repair\js\views\status-quo\workshop-flow-assessment.js:603 +msgid "Region" +msgstr "Régió" + +#: .\repair\js\views\status-quo\workshop-flow-assessment.js:661 +#: .\repair\js\views\status-quo\workshop-flow-assessment.js:662 +#: .\repair\js\views\targets\flow-targets.js:227 +msgid "Focus Area" +msgstr "Fókuszterület" + +#: .\repair\js\views\status-quo\workshop-flow-assessment.js:672 +#: .\repair\js\views\status-quo\workshop-flow-assessment.js:673 +#: .\repair\js\views\targets\flow-targets.js:227 +msgid "Casestudy Region" +msgstr "Esettanulmány Régió" + +#: .\repair\js\views\strategy\setup-solution-part.js:327 +#: .\repair\js\views\strategy\setup-solution-part.js:344 +#: .\repair\js\views\strategy\setup-solution-part.js:382 +#: .\repair\js\views\strategy\setup-solutions-logic.js:218 +msgid "Select" +msgstr "válasszon" + +#: .\repair\js\views\strategy\setup-solutions-logic.js:181 +msgid "successfully cloned" +msgstr "sikeres klónozás" + +#: .\repair\js\views\strategy\setup-solutions-logic.js:264 +msgid "clone item" +msgstr "tétel klónozása" + +#: .\repair\js\views\strategy\setup-solutions-logic.js:405 +msgid "GeoJSON needs attributes \"type\" and \"coordinates\"" +msgstr "A GeoJSON-nak attribútumokra és \"koordinátákat\" van szüksége" + +#: .\repair\js\views\strategy\setup-solutions-logic.js:408 +msgid "type has to be MultiPolygon or Polygon" +msgstr "típusnak MultiPolygonnak vagy sokszögnek kell lennie" + +#: .\repair\js\views\strategy\setup-solutions.js:241 +msgid "Do you want to delete the category?" +msgstr "Törli a kategóriát?" + +#: .\repair\js\views\strategy\setup-solutions.js:327 +msgid "Do you want to delete the selected solution?" +msgstr "Valóban törli a kiválasztott megoldást?" + +#: .\repair\js\views\strategy\strategy.js:248 +msgid "Do you really want to delete your solution?" +msgstr "Valóban törli a megoldást?" + +#: .\repair\js\views\strategy\strategy.js:640 +#: .\repair\js\views\strategy\strategy.js:646 +msgid "possible implementation area" +msgstr "lehetséges megvalósítási terület" + +#: .\repair\js\views\study-area\charts.js:76 +msgid "The charts are not set up." +msgstr "A diagramok nincsenek beállítva." + +#: .\repair\js\views\study-area\charts.js:354 +msgid "Do you really want to delete the selected chart?" +msgstr "Tényleg törölni szeretné a kiválasztott diagramot?" + +#: .\repair\js\views\study-area\charts.js:355 +msgid "Do you really want to delete the selected category and all its charts?" +msgstr "" +"Tényleg törölni szeretné a kiválasztott kategóriát és az összes diagramját?" + +#: .\repair\js\views\study-area\keyflows.js:92 +msgid "Upload" +msgstr "feltöltés" + +#: .\repair\js\views\study-area\setup-maps.js:293 +msgid "Do you really want to delete the selected category and all its layers?" +msgstr "Valóban törölni akarja a kiválasztott szereplőt és rétegeit? " + +#: .\repair\js\views\study-area\stakeholders.js:87 +msgid "The stakeholders are not set up." +msgstr "a szereplők nincsenek beállítva" + +#: .\repair\js\views\study-area\stakeholders.js:131 +msgid "Stakeholder" +msgstr "szereplő" + +#: .\repair\js\views\study-area\stakeholders.js:132 +msgid "add stakeholder" +msgstr "szereplő hozzáadása" + +#: .\repair\js\views\study-area\stakeholders.js:141 +msgid "Remove category" +msgstr "kategória eltávolítása" + +#: .\repair\js\views\study-area\stakeholders.js:152 +msgid "Edit category" +msgstr "kategória szerkesztése" + +#: .\repair\js\views\study-area\stakeholders.js:226 +msgid "Add Stakeholder" +msgstr "Szereplő hozzáadása" + +#: .\repair\js\views\study-area\stakeholders.js:258 +msgid "Edit Stakeholder" +msgstr "szereplő szerkesztése" + +#: .\repair\js\views\study-area\stakeholders.js:288 +msgid "Do you want to delete the selected stakeholder?" +msgstr "Valóban törli a kiválasztott szereplőt?" + +#: .\repair\js\views\study-area\stakeholders.js:304 +msgid "Add Stakeholder Category" +msgstr "Szereplőcsoport hozzáadása" -#: .\repair\templates\studyarea\stakeholders.html:17 -msgid "Waste Companies" +#: .\repair\js\views\study-area\stakeholders.js:311 +msgid "Do you really want to delete the stakeholder category?" +msgstr "Tényleg törölni kívánja az érintettek kategóriáját?" + +#: .\repair\js\views\study-area\stakeholders.js:337 +msgid "Edit Category" +msgstr "Kategória szerkesztése" + +#: .\repair\js\views\study-area\workshop-maps.js:204 +msgid "The map is not set up." +msgstr "A térkép nincs beállítva." + +#: .\repair\js\views\study-area\workshop-maps.js:468 +msgid "legend not found" +msgstr "legenda nem található" + +#: .\repair\js\views\study-area\workshop-maps.js:508 +msgid "Layer" +msgstr "réteg" + +#: .\repair\js\views\targets\flow-targets.js:193 +msgid "Remove target" +msgstr "cél eltávolítása" + +#: .\repair\js\views\targets\flow-targets.js:300 +msgid "" +"No indicators available for the target definition. Please contact your " +"workshop leader." msgstr "" +"Nincsenek elérhető mutatók a célmeghatározáshoz. Kérjük, forduljon a " +"műhelyvezetőhöz." -#: .\repair\templates\studyarea\stakeholders.html:26 -msgid "NGOs" +#: .\repair\js\views\targets\flow-targets.js:337 +msgid "" +"Indicator is duplicate in this objective. Please remove or set another " +"indicator." msgstr "" +"A mutató ebben a célkitűzésben ismétlődik. Kérjük, távolítson el vagy " +"állítson be egy másik mutatót." + +#: .\repair\js\views\targets\ranking-objectives.js:59 +msgid "Ranking general objectives" +msgstr "Az általános célok rangsorolása" + +#: .\repair\js\views\targets\ranking-objectives.js:61 +msgid "Ranking objectives for the keyflow " +msgstr "A kulcsáramlási célok rangsorolása " + +#: .\repair\settings.py:168 +msgid "English" +msgstr "angol " + +#: .\repair\settings.py:169 +msgid "German" +msgstr "német " + +#: .\repair\settings.py:170 +msgid "Dutch" +msgstr "holland " + +#: .\repair\settings.py:171 +msgid "Polish" +msgstr "lengyel " + +#: .\repair\settings.py:172 +msgid "Hungarian" +msgstr "magyar " + +#: .\repair\settings.py:173 +msgid "Italian" +msgstr "olasz " + +#: .\repair\static\bundles\prod\0.js:1 +msgid "brush" +msgstr "ecset" + +#: .\repair\static\bundles\prod\0.js:1 +msgid "brushend" +msgstr "az ecset bezárása" + +#: .\repair\static\bundles\prod\0.js:1 +msgid "brushstart" +msgstr "az ecset megnyitása" + +#: .\src\django-publications-bootstrap\publications_bootstrap\admin\publicationadmin.py:112 +msgid "Mark selected %(verbose_name_plural)s as drafts" +msgstr "A kiválasztott % (verbose_name_plural) megjelölése vázlatként" + +#: .\src\django-publications-bootstrap\publications_bootstrap\admin\publicationadmin.py:117 +msgid "Mark selected %(verbose_name_plural)s as submitted" +msgstr "A kiválasztott % (verbose_name_plural) megjelölése benyújtottként" + +#: .\src\django-publications-bootstrap\publications_bootstrap\admin\publicationadmin.py:122 +msgid "Mark selected %(verbose_name_plural)s as accepted" +msgstr "A kiválasztott % (verbose_name_plural) megjelölése elfogadottként" + +#: .\src\django-publications-bootstrap\publications_bootstrap\admin\publicationadmin.py:127 +msgid "Mark selected %(verbose_name_plural)s as published" +msgstr "A kiválasztott % (verbose_name_plural) megjelölése közzétettként" + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:41 +msgid "January" +msgstr "január " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:42 +msgid "February" +msgstr "február " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:43 +msgid "March" +msgstr "március " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:44 +msgid "April" +msgstr "Április " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:45 +msgid "May" +msgstr "május " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:46 +msgid "June" +msgstr "június " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:47 +msgid "July" +msgstr "július " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:48 +msgid "August" +msgstr "augusztus " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:49 +msgid "September" +msgstr "szeptember " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:50 +msgid "October" +msgstr "október " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:51 +msgid "November" +msgstr "november " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:52 +msgid "December" +msgstr "december " + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:68 +msgid "draft" +msgstr "vázlat" + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:69 +msgid "submitted" +msgstr "benyújtva" + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:70 +msgid "accepted" +msgstr "elfogadva" + +#: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:71 +msgid "published" +msgstr "közzétett" diff --git a/repair/locale/hu/LC_MESSAGES/djangojs.po b/repair/locale/hu/LC_MESSAGES/djangojs.po index 4cb897fd9..fa0e5ce0f 100644 --- a/repair/locale/hu/LC_MESSAGES/djangojs.po +++ b/repair/locale/hu/LC_MESSAGES/djangojs.po @@ -3,46 +3,43 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#: .\node_modules\d3-selection\dist\d3-selection.min.js:2 -#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:25 -#: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2019-05-29 10:52+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" +"PO-Revision-Date: 2019-07-19 11:55+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Last-Translator: \n" +"Language-Team: \n" +"X-Generator: Poedit 2.2.3\n" +"Language: hu_HU\n" #: .\docs\views_baseview.js.html:181 .\repair\js\views\common\baseview.js:176 msgid "Warning" -msgstr "" +msgstr "Figyelmeztetés" #: .\docs\views_baseview.js.html:197 .\repair\js\views\common\baseview.js:299 msgid "The server responded with: " -msgstr "" +msgstr "A kiszolgáló válaszol " #: .\docs\views_baseview.js.html:198 msgid "Server does not respond." -msgstr "" +msgstr "A kiszolgáló nem válaszol." #: .\docs\views_baseview.js.html:199 #: .\docs\views_data-entry_edit-node.js.html:646 #: .\repair\js\views\data-entry\bulk-upload.js:399 #: .\repair\js\views\data-entry\edit-node.js:697 .\repair\js\welcome.js:119 msgid "Error" -msgstr "" +msgstr "Hiba" #: .\docs\views_changes_solutions.js.html:107 msgid "Solution" -msgstr "" +msgstr "Megoldás" #: .\docs\views_changes_solutions.js.html:168 #: .\docs\views_changes_solutions.js.html:169 @@ -57,23 +54,23 @@ msgstr "" #: .\repair\js\views\data-entry\edit-actor.js:825 #: .\repair\js\views\strategy\setup-solutions-logic.js:427 msgid "Focus area" -msgstr "" +msgstr "Fókuszterület" #: .\docs\views_data-entry_actors.js.html:132 #: .\docs\views_data-entry_edit-node.js.html:797 #: .\repair\js\views\data-entry\edit-node.js:868 msgid "Search" -msgstr "" +msgstr "Keresés" #: .\docs\views_data-entry_actors.js.html:252 #: .\repair\js\views\data-entry\actors-flows.js:389 msgid "Add Actor" -msgstr "" +msgstr "Szereplő hozzáadása" #: .\docs\views_data-entry_actors.js.html:264 #: .\repair\js\views\data-entry\actors-flows.js:399 msgid "Do you really want to delete the actor" -msgstr "" +msgstr "Biztosan törölni akarja a szereplőt" #: .\docs\views_data-entry_actors.js.html:289 #: .\docs\views_data-entry_materials.js.html:308 @@ -87,12 +84,12 @@ msgstr "" #: .\repair\js\views\study-area\stakeholders.js:230 #: .\repair\js\views\study-area\stakeholders.js:263 msgid "Name" -msgstr "" +msgstr "Név" #: .\docs\views_data-entry_edit-actor.js.html:496 #: .\repair\js\views\data-entry\edit-actor.js:490 msgid "select an area" -msgstr "" +msgstr "Válasszon területet " #: .\docs\views_data-entry_edit-node.js.html:268 #: .\docs\views_flowsankey.js.html:190 .\docs\views_flowsankey.js.html:206 @@ -101,69 +98,69 @@ msgstr "" #: .\repair\js\views\data-entry\edit-node.js:258 #: .\repair\js\views\strategy\setup-question.js:67 msgid "t/year" -msgstr "" +msgstr "t/év" #: .\docs\views_data-entry_edit-node.js.html:306 #: .\repair\js\views\common\flowsankeymap.js:674 #: .\repair\js\views\data-entry\edit-node.js:328 msgid "Waste" -msgstr "" +msgstr "Hulladék" #: .\docs\views_data-entry_edit-node.js.html:309 #: .\repair\js\views\common\flowsankeymap.js:674 #: .\repair\js\views\data-entry\edit-node.js:331 msgid "Product" -msgstr "" +msgstr "Termék" #: .\docs\views_data-entry_edit-node.js.html:323 #: .\repair\js\views\data-entry\edit-node.js:349 msgid "Composition" -msgstr "" +msgstr "Összetétel" #: .\docs\views_data-entry_edit-node.js.html:439 msgid "edit datasource" -msgstr "" +msgstr "adatforrás szerkesztése" #: .\docs\views_data-entry_edit-node.js.html:495 #: .\docs\views_data-entry_edit-node.js.html:655 #: .\repair\js\views\data-entry\edit-node.js:534 #: .\repair\js\views\data-entry\edit-node.js:706 msgid "custom" -msgstr "" +msgstr "általános" #: .\docs\views_data-entry_edit-node.js.html:573 #: .\repair\js\views\data-entry\edit-node.js:630 msgid "remove fraction" -msgstr "" +msgstr "a frakció eltávolítása" #: .\docs\views_data-entry_edit-node.js.html:626 #: .\repair\js\views\data-entry\edit-node.js:683 msgid "The fractions have to sum up to 100!" -msgstr "" +msgstr "A frakciók összege max. 100 lehet!" #: .\docs\views_data-entry_edit-node.js.html:626 #: .\repair\js\views\data-entry\edit-node.js:683 msgid "current sum" -msgstr "" +msgstr "jelenlegi érték" #: .\docs\views_data-entry_edit-node.js.html:634 #: .\repair\js\views\data-entry\edit-node.js:690 msgid "All materials have to be set!" -msgstr "" +msgstr "Minden anyagot be kell állítani!" #: .\docs\views_data-entry_edit-node.js.html:638 msgid "Multiple fractions with the same material are not allowed!" -msgstr "" +msgstr "Több frakció egyazon anyaggal nem megengedett!" #: .\docs\views_data-entry_materials.js.html:215 #: .\repair\js\views\data-entry\materials.js:207 msgid "Add Material" -msgstr "" +msgstr "Anyag hozzáadása" #: .\docs\views_data-entry_materials.js.html:244 #: .\repair\js\views\data-entry\materials.js:248 msgid "Edit Material" -msgstr "" +msgstr "Anyag szerkesztése" #: .\docs\views_data-entry_materials.js.html:262 #: .\repair\js\views\data-entry\materials.js:278 @@ -171,708 +168,711 @@ msgid "" "Do you really want to delete the selected material and all of its children " "from the database?" msgstr "" +"Biztosan törölni szeretné a kiválasztott anyagot és az összes számaztatott részt az " +"adatbázisból?" #: .\docs\views_status-quo_flows.js.html:163 #: .\repair\js\views\common\filter-flows.js:380 #: .\repair\js\views\status-quo\edit-indicator-flow.js:251 msgid "All" -msgstr "" +msgstr "Mind" #: .\docs\views_study-area_charts.js.html:157 #: .\docs\views_study-area_setup-maps.js.html:272 #: .\repair\js\views\study-area\charts.js:295 #: .\repair\js\views\study-area\setup-maps.js:223 msgid "Add Category" -msgstr "" +msgstr "Kategória hozzáadása" #: .\docs\views_study-area_setup-maps.js.html:115 #: .\repair\js\views\status-quo\objectives.js:106 msgid "Remove" -msgstr "" +msgstr "Törlés" #: .\docs\views_study-area_setup-maps.js.html:331 #: .\repair\js\views\study-area\setup-maps.js:292 msgid "Do you really want to delete the selected layer?" -msgstr "" +msgstr "Biztosan eltávolítja a kiválasztott réteget?" #: .\docs\views_study-area_setup-maps.js.html:332 msgid "Do you really want to delete the selected category?" -msgstr "" +msgstr "Biztosan eltávoítja a kiválasztott kategóriát?" #: .\docs\views_study-area_setup-maps.js.html:394 #: .\repair\js\views\study-area\charts.js:417 #: .\repair\js\views\study-area\setup-maps.js:351 msgid "Edit Name" -msgstr "" +msgstr "Név szerkesztése" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:25 #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid " " -msgstr "" +msgstr " " #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:25 #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "." -msgstr "" +msgstr "." #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "*" -msgstr "" +msgstr "*" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "import" -msgstr "" +msgstr "importálás" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "{}" -msgstr "" +msgstr "{}" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid ", " -msgstr "" +msgstr ", " #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "this" -msgstr "" +msgstr "ez" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "super" -msgstr "" +msgstr "szuper" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "null" -msgstr "" +msgstr "hiányzó érték" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid ",\n" -msgstr "" +msgstr ",\n" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid " = " -msgstr "" +msgstr " = " #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "\n" -msgstr "" +msgstr "\n" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "debugger;" -msgstr "" +msgstr "hibakereső " #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid ":" -msgstr "" +msgstr ":" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "empty" -msgstr "" +msgstr "üres" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "any" -msgstr "" +msgstr "bármely" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "mixed" -msgstr "" +msgstr "vegyes" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "boolean" -msgstr "" +msgstr "logikai" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "interface " -msgstr "" +msgstr "interfész " #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid " & " -msgstr "" +msgstr " & " #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "number" -msgstr "" +msgstr "szám" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "string" -msgstr "" +msgstr "string" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "typeof " -msgstr "" +msgstr "típusú" #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid " | " -msgstr "" +msgstr " | " #: .\node_modules\get-down\node_modules\ajv\dist\regenerator.min.js:26 msgid "void" -msgstr "" +msgstr "üres, hiányos" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "using url" -msgstr "" +msgstr "url használat" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "_receiveInfo" -msgstr "" +msgstr "_receiveInfo" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "info" -msgstr "" +msgstr "információ" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid " enabled transports" -msgstr "" +msgstr " engedélyezett szállítás" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "attempt" -msgstr "" +msgstr "kísérlet" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "waiting for body" -msgstr "" +msgstr "waiting for body" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "using timeout" -msgstr "" +msgstr "időtúllépés használata" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "transport url" -msgstr "" +msgstr "szállítási url" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "_transportTimeout" -msgstr "" +msgstr "szállításiIdőtúllépés " #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "_transportMessage" -msgstr "" +msgstr "szállításiÜzenet" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "heartbeat" -msgstr "" +msgstr "heartbeat" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "bad json" -msgstr "" +msgstr "rossz json" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "message" -msgstr "" +msgstr "üzenet" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "empty payload" -msgstr "" +msgstr "üres hasznos teher " #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "_transportClose" -msgstr "" +msgstr "szállításBezárás " #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "_open" -msgstr "" +msgstr "_megnyitás" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "connected" -msgstr "" +msgstr "kapcsolódva" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "_close" -msgstr "" +msgstr "_bezárás" #: .\node_modules\webpack-dev-server\client\index.bundle.js:1 msgid "disconnected" -msgstr "" +msgstr "szétkapcsolás" #: .\repair\apps\admin.py:51 msgid "The requested admin page is " -msgstr "" +msgstr "A kért admin oldal " #: .\repair\apps\asmfa\serializers\bulkcreate.py:136 msgid "Flows from an actor to itself are not allowed." -msgstr "" +msgstr "A aktor átal saját magához való áramoltatás nem megengedett." #: .\repair\apps\asmfa\serializers\bulkcreate.py:263 msgid "fractions per composition have to sum up to 1.0 with " -msgstr "" +msgstr "a frakció / kompozíciók összege legfeljebb 1,0 lehet az alábbival " #: .\repair\apps\asmfa\serializers\keyflows.py:41 msgid "Select the Casestudies the Keyflow is used in" -msgstr "" +msgstr "Válassza ki az esettanulmányt amelyben az Anyagáramot használni kívánja" #: .\repair\apps\asmfa\serializers\locations.py:58 msgid "Actor <{}> already has an administrative location " -msgstr "" +msgstr "A szereplő már rendelkezik adminisztratív hellyel " #: .\repair\apps\asmfa\serializers\nodes.py:66 msgid "This field is required." -msgstr "" +msgstr "kötelezenően kitöltendő mező " #: .\repair\apps\asmfa\serializers\nodes.py:67 msgid "Invalid Actor ID - Object does not exist." -msgstr "" +msgstr "Érvénytelen ID -az objektum nem létezik." #: .\repair\apps\asmfa\serializers\nodes.py:68 msgid "This field may not be null." -msgstr "" +msgstr "A mező értéke nem lehet nulla." #: .\repair\apps\asmfa\serializers\nodes.py:177 msgid "Enter a valid URL (or leave it blank)." -msgstr "" +msgstr "Adjon meg egy érvényes URL-t (vagy hagyja üresen)." #: .\repair\apps\asmfa\views\flowfilter.py:172 #: .\repair\apps\statusquo\views\indicators.py:425 msgid "calculation is not done yet" -msgstr "" +msgstr "számítás még nincs kész" #: .\repair\apps\asmfa\views\flowfilter.py:175 #: .\repair\apps\statusquo\views\indicators.py:428 msgid "calculation is still in process" -msgstr "" +msgstr "A számítás folyamatban van. " #: .\repair\apps\asmfa\views\keyflows.py:243 msgid "This material is a default material " -msgstr "" +msgstr "Alapértelmezett anyag " #: .\repair\apps\changes\models\solutions.py:20 msgid "Enter only floats separated by commas." -msgstr "" +msgstr "Vesszővel válassza el a bevitt tételeket." #: .\repair\apps\changes\serializers\strategies.py:105 msgid "not calculated yet" -msgstr "" +msgstr "számítás alatt" #: .\repair\apps\changes\serializers\strategies.py:109 msgid "calculation started" -msgstr "" +msgstr "A szímátás elkezdődött. " #: .\repair\apps\changes\serializers\strategies.py:111 #: .\repair\apps\changes\serializers\strategies.py:118 msgid "(server time)" -msgstr "" +msgstr "(szerveridő)" #: .\repair\apps\changes\serializers\strategies.py:112 msgid "elapsed" -msgstr "" +msgstr "lejárt az idő" #: .\repair\apps\changes\serializers\strategies.py:116 msgid "calculation finished" -msgstr "" +msgstr "a számítás elkészült" #: .\repair\apps\changes\views\strategies.py:75 msgid "The base data is not set up. " -msgstr "" +msgstr "Az alapadatok nincsenek beállítva. " #: .\repair\apps\login\serializers\bases.py:109 msgid "User {} has no permission to access casestudy {}" -msgstr "" +msgstr "A felhasználónak nincs hozzáférése az esettanulmányhoz. " #: .\repair\apps\login\serializers\users.py:40 msgid "Select the Casestudies the user works on" -msgstr "" +msgstr "Az esettanulmány kiválasztása amelyen a felhasználó dolgozik" #: .\repair\apps\login\views\users.py:124 #: .\repair\apps\login\views\users.py:171 #: .\repair\apps\login\views\users.py:195 #: .\repair\apps\login\views\users.py:200 msgid "Unauthorized" -msgstr "" +msgstr "nem engedélyezett " #: .\repair\apps\statusquo\views\indicators.py:243 msgid "SUM aggregation Flow A" -msgstr "" +msgstr "SUM aggregáció Flow A" #: .\repair\apps\statusquo\views\indicators.py:244 msgid "Flow A" -msgstr "" +msgstr "A áram" #: .\repair\apps\statusquo\views\indicators.py:245 msgid "t / year" -msgstr "" +msgstr "t / év" #: .\repair\apps\statusquo\views\indicators.py:261 msgid "SUM aggregation Flow A / SUM aggregation Flow B" -msgstr "" +msgstr "SUM \"A\" ggregációs áram / SUM \"B\" aggregációs áram " #: .\repair\apps\statusquo\views\indicators.py:262 msgid "(Flow A / Flow B) * 100" -msgstr "" +msgstr "(A áram / B áram ) * 110" #: .\repair\apps\statusquo\views\indicators.py:298 msgid "SUM aggregation Flow A / Inhabitants in Area" -msgstr "" +msgstr "SUM aggregáció Flow A / lakosok a területen" #: .\repair\apps\statusquo\views\indicators.py:299 msgid "Flow A / Inhabitants" -msgstr "" +msgstr "A áram / lakosok" #: .\repair\apps\statusquo\views\indicators.py:300 msgid "kg / inhabitant and year" -msgstr "" +msgstr "kg / lakos és év" #: .\repair\apps\statusquo\views\indicators.py:341 msgid "SUM aggregation Flow A / geometrical area in hectar" -msgstr "" +msgstr "SUM aggregáció Áramlás A / geometriai terület hektárban" #: .\repair\apps\statusquo\views\indicators.py:342 msgid "Flow A / Area (ha)" -msgstr "" +msgstr "A áram / terület (ha)" #: .\repair\apps\statusquo\views\indicators.py:343 msgid "t / hectar and year" -msgstr "" +msgstr "t / hektár és év" #: .\repair\apps\studyarea\serializers\areas.py:135 msgid "you may only pass parent_area_id OR " -msgstr "" +msgstr "csak a parent area id-jét érheti el VAGY " #: .\repair\apps\studyarea\serializers\areas.py:148 msgid "parent_level is required when relating to " -msgstr "" +msgstr "a parent_levelre akkor van szükség, ha az" #: .\repair\apps\utils\serializers.py:417 msgid "unsupported filetype" -msgstr "" +msgstr "nem támogatott fájltípus " #: .\repair\apps\utils\serializers.py:422 msgid "wrong file-encoding ({} used)" -msgstr "" +msgstr "rossz fájlkódolás ({} használva)" #: .\repair\apps\utils\serializers.py:457 msgid "Index column(s) missing: {}" -msgstr "" +msgstr "Az index oszlop (ok) hiányzik: {}" #: .\repair\apps\utils\serializers.py:465 msgid "Index \"{}\" has to be unique!" -msgstr "" +msgstr "Index {{} \" csak önmagában állhat!" #: .\repair\apps\utils\serializers.py:468 msgid "The combination of indices \"{}\" have to be unique!" -msgstr "" +msgstr "A \"{}\" indexek kombinációjának egyedinek kell lennie!" #: .\repair\apps\utils\serializers.py:470 msgid "Duplicates found: {}" -msgstr "" +msgstr "A másolatok megtalálhatók: {}" #: .\repair\apps\utils\serializers.py:572 msgid "invalid geometry" -msgstr "" +msgstr "érvénytelen geometria " #: .\repair\apps\utils\serializers.py:575 msgid "Invalid geometries" -msgstr "" +msgstr "Érvénytelen geometriák " #: .\repair\apps\utils\serializers.py:588 msgid "Integer expected: number without decimals" -msgstr "" +msgstr "Egész szám szükséges: szám tizedes nélkül" #: .\repair\apps\utils\serializers.py:592 msgid "Float expected: number with or without " -msgstr "" +msgstr "Folytonos szám szükséges: szám vagy az alábbival, vagy anélkül" #: .\repair\apps\utils\serializers.py:597 msgid "Boolean expected (\"true\" or \"false\")" -msgstr "" +msgstr "Logikai válasz szükséges (\"igaz\" or \"hamis\")" #: .\repair\apps\utils\serializers.py:601 msgid "Number format errors" -msgstr "" +msgstr "A számformátum hibás" #: .\repair\apps\utils\serializers.py:628 msgid "Column(s) missing: {}" -msgstr "" +msgstr "Oszlop(ok) hiányzik: {}" #: .\repair\apps\utils\serializers.py:652 msgid "{c} - related models {m} not found" -msgstr "" +msgstr "{c} - a vonatkozó modellek {m} nem találhatóak" #: .\repair\apps\utils\serializers.py:655 msgid "relation not found" -msgstr "" +msgstr "kapcsolat nem található" #: .\repair\apps\utils\views.py:303 msgid "Referencing Object(s)" -msgstr "" +msgstr "Referencia tárgy(ak)" #: .\repair\js\conclusions.js:63 msgid "There are no specified users! Please go to setup mode." -msgstr "" +msgstr "Nincs felhasználó megjelölve! Használja a setup módot" #: .\repair\js\strategy.js:96 msgid "Calculation started. Please wait till it is finished (check Status)." -msgstr "" +msgstr "A számolás megkezdődött. Kérjük várjon amíg befejeződik (figyelje a státuszt)" #: .\repair\js\strategy.js:140 msgid "Graph was successfully build." -msgstr "" +msgstr "Az ábra sikeresen felépítésre került" #: .\repair\js\targets.js:52 msgid "" "Flow targets can't be set for general objectives.

Please select a " "keyflow inside the side-menu." -msgstr "" +msgstr "Az áram célját nem sikerült általános térgyra beállítani

Kérjük, válaszzon egy " +"kulcsáramot az oldalsó menü használatával" #: .\repair\js\views\common\baseview.js:278 msgid "Info" -msgstr "" +msgstr "Információ" #: .\repair\js\views\common\baseview.js:300 msgid "Error " -msgstr "" +msgstr "Hiba " #: .\repair\js\views\common\filter-flows.js:392 #: .\repair\js\views\data-entry\materials.js:77 #: .\repair\js\views\status-quo\edit-indicator-flow.js:263 msgid "flows" -msgstr "" +msgstr "áram" #: .\repair\js\views\common\filter-flows.js:399 msgid "too many to display" -msgstr "" +msgstr "túl sokat kellene egyszerre megjeleníteni" #: .\repair\js\views\common\filter-flows.js:485 #: .\repair\js\views\status-quo\edit-indicator-flow.js:305 msgid "All materials" -msgstr "" +msgstr "Minden anyagfajta" #: .\repair\js\views\common\flowsankey.js:92 msgid "No flow data found for applied filters." -msgstr "" +msgstr "Nem található adat a kiválasztott áramhoz" #: .\repair\js\views\common\flowsankey.js:174 #: .\repair\js\views\common\flowsankeymap.js:559 msgid "Actor" -msgstr "" +msgstr "Szereplő" #: .\repair\js\views\common\flowsankey.js:210 msgid "avoidable" -msgstr "" +msgstr "elkerülhető" #: .\repair\js\views\common\flowsankey.js:222 #: .\repair\js\views\common\flowsankeymap.js:675 msgid "Process" -msgstr "" +msgstr "Folyamat" #: .\repair\js\views\common\flowsankey.js:308 #: .\repair\js\views\strategy\setup-solution-part.js:107 #: .\repair\js\views\strategy\setup-solution-part.js:216 msgid "origin" -msgstr "" +msgstr "eredet" #: .\repair\js\views\common\flowsankey.js:308 msgid "origin_code" -msgstr "" +msgstr "eredet_kód" #: .\repair\js\views\common\flowsankey.js:309 #: .\repair\js\views\strategy\setup-solution-part.js:107 #: .\repair\js\views\strategy\setup-solution-part.js:216 msgid "destination" -msgstr "" +msgstr "desztináció" #: .\repair\js\views\common\flowsankey.js:309 msgid "destination_code" -msgstr "" +msgstr "desztináció kód" #: .\repair\js\views\common\flowsankey.js:310 msgid "amount" -msgstr "" +msgstr "összeg" #: .\repair\js\views\common\flowsankey.js:310 msgid "composition" -msgstr "" +msgstr "összetétel" #: .\repair\js\views\common\flowsankey.js:318 #: .\repair\js\views\common\flowsankeymap.js:727 msgid "Stock" -msgstr "" +msgstr "Helyben maradó" #: .\repair\js\views\common\flowsankeymap.js:131 msgid "Display materials" -msgstr "" +msgstr "Mutasd az anyagot" #: .\repair\js\views\common\flowsankeymap.js:132 msgid "Animate flows" -msgstr "" +msgstr "Az áram animációja" #: .\repair\js\views\common\flowsankeymap.js:133 msgid "Cluster locations" -msgstr "" +msgstr "A klaszter lokalizációja" #: .\repair\js\views\common\flowsankeymap.js:134 msgid "Show actors" -msgstr "" +msgstr "szereplők megmutatása" #: .\repair\js\views\common\flowsankeymap.js:135 msgid "Show flows" -msgstr "" +msgstr "áramok megmutatása" #: .\repair\js\views\common\flowsankeymap.js:136 msgid "Show stocks" -msgstr "" +msgstr "Helyben maradó anyagok megmutatása" #: .\repair\js\views\common\flowsankeymap.js:522 msgid "actors" -msgstr "" +msgstr "szereplők" #: .\repair\js\views\common\flowsankeymap.js:563 msgid "Actor referenced by flow, but missing a location:" -msgstr "" +msgstr "Szereplő amelyre az áramlás vonatkozik, de hiányó hely:" #: .\repair\js\views\conclusions\flow-targets.js:112 msgid "Objectives for key flow " -msgstr "" +msgstr "A kulcs áram célpontjai " #: .\repair\js\views\conclusions\flow-targets.js:158 msgid "Indicators used as target setting in the key flow " -msgstr "" +msgstr "A célállapothoz használt indikátorok a kulcs áramban " #: .\repair\js\views\conclusions\manage-notepad.js:145 #: .\repair\js\views\status-quo\objectives.js:309 msgid "Edit" -msgstr "" +msgstr "szerkesztés" #: .\repair\js\views\conclusions\manage-notepad.js:163 msgid "Do you want to delete the item?" -msgstr "" +msgstr "Törölni akarja az elemet?" #: .\repair\js\views\conclusions\manage-notepad.js:173 #: .\repair\js\views\status-quo\objectives.js:339 #: .\repair\js\views\strategy\setup-solutions-logic.js:295 msgid "Do you want to delete the selected item?" -msgstr "" +msgstr "Szeretné törölni a kiválasztott elemet?" #: .\repair\js\views\conclusions\objectives.js:66 msgid "Objectives for keyflow " -msgstr "" +msgstr "A kulcs áram céljai" #: .\repair\js\views\conclusions\objectives.js:68 msgid "General objectives" -msgstr "" +msgstr "Általános célok" #: .\repair\js\views\data-entry\bulk-upload.js:55 #: .\repair\js\views\data-entry\edit-node.js:825 #: .\repair\js\views\data-entry\edit-node.js:831 msgid "Activity Group" -msgstr "" +msgstr "tevékenységcsoport" #: .\repair\js\views\data-entry\bulk-upload.js:56 msgid "Activities" -msgstr "" +msgstr "Tevékenységek" #: .\repair\js\views\data-entry\bulk-upload.js:57 msgid "Actors" -msgstr "" +msgstr "Szereplők" #: .\repair\js\views\data-entry\bulk-upload.js:58 msgid "Actor Locations" -msgstr "" +msgstr "Szereplő helyzete" #: .\repair\js\views\data-entry\bulk-upload.js:59 msgid "Materials" -msgstr "" +msgstr "Anyagok" #: .\repair\js\views\data-entry\bulk-upload.js:60 msgid "Products" -msgstr "" +msgstr "Termékek" #: .\repair\js\views\data-entry\bulk-upload.js:61 msgid "Wastes" -msgstr "" +msgstr "Hulladékok" #: .\repair\js\views\data-entry\bulk-upload.js:62 msgid "Actor to Actor Flows" -msgstr "" +msgstr "Szereplőtől szereplőhöz áramlás" #: .\repair\js\views\data-entry\bulk-upload.js:63 msgid "Actor Stocks" -msgstr "" +msgstr "Tároló szereplők" #: .\repair\js\views\data-entry\bulk-upload.js:66 msgid "Area Levels" -msgstr "" +msgstr "Területi szintek" #: .\repair\js\views\data-entry\bulk-upload.js:67 msgid "Areas" -msgstr "" +msgstr "Területek" #: .\repair\js\views\data-entry\bulk-upload.js:68 msgid "Publications" -msgstr "" +msgstr "Publikációk" #: .\repair\js\views\data-entry\bulk-upload.js:106 msgid "Upload bibtex files" -msgstr "" +msgstr "Bibtex fájl feltöltés" #: .\repair\js\views\data-entry\bulk-upload.js:108 msgid "here" -msgstr "" +msgstr "Itt" #: .\repair\js\views\data-entry\bulk-upload.js:137 msgid "Focusarea updated" -msgstr "" +msgstr "A fókuszterület aktualizálva lett" #: .\repair\js\views\data-entry\bulk-upload.js:141 msgid "Focusarea update failed" -msgstr "" +msgstr "A fókuszterület aktualizálása nem sikerült" #: .\repair\js\views\data-entry\bulk-upload.js:157 msgid "Casestudy Region updated" -msgstr "" +msgstr "Esettanulmány régió frissült " #: .\repair\js\views\data-entry\bulk-upload.js:161 msgid "Casestudy Region update failed" -msgstr "" +msgstr "Esettanulmány régió frissítése sikertelen" #: .\repair\js\views\data-entry\bulk-upload.js:188 msgid "Do you really want to delete the keyflow and ALL of its data?" -msgstr "" +msgstr "Biztosan törölni akarja a kulcsáramot és ALL adatait" #: .\repair\js\views\data-entry\bulk-upload.js:191 msgid "Are you sure?" -msgstr "" +msgstr "Biztos benne?" #: .\repair\js\views\data-entry\bulk-upload.js:213 msgid "Removing data" -msgstr "" +msgstr "Adatok törlése" #: .\repair\js\views\data-entry\bulk-upload.js:218 msgid "Nothing to remove" -msgstr "" +msgstr "Semmit nem töröl" #: .\repair\js\views\data-entry\bulk-upload.js:227 msgid "entries removed" -msgstr "" +msgstr "a beírás törlésre került" #: .\repair\js\views\data-entry\bulk-upload.js:228 msgid "entries failed to remove" -msgstr "" +msgstr "a beírás kitörlése nem sikerült" #: .\repair\js\views\data-entry\bulk-upload.js:265 msgid "Successfully deleted" -msgstr "" +msgstr "Sikeresen törölve" #: .\repair\js\views\data-entry\bulk-upload.js:293 msgid "Force CASCADED deletion (delete referencing objects, even if protected)" -msgstr "" +msgstr "CASCADED törlés erőltetése (törli a referencia célokat, akkor is, ha azok védettek)" #: .\repair\js\views\data-entry\bulk-upload.js:299 msgid "Do you really want to delete the existing data and the related data?" -msgstr "" +msgstr "Biztosan törölni akarja a létező és kapcsolódó adatokat?" #: .\repair\js\views\data-entry\bulk-upload.js:333 msgid "No file selected to upload!" -msgstr "" +msgstr "Nem választotta ki a feltöltendő fájlt" #: .\repair\js\views\data-entry\bulk-upload.js:348 msgid "Uploading" -msgstr "" +msgstr "töltés" #: .\repair\js\views\data-entry\bulk-upload.js:356 msgid "Created models:" -msgstr "" +msgstr "létrehozott modellek" #: .\repair\js\views\data-entry\bulk-upload.js:361 msgid "Updated models:" -msgstr "" +msgstr "frissített modellek" #: .\repair\js\views\data-entry\bulk-upload.js:371 #: .\repair\js\views\status-quo\setup-flow-assessment.js:198 @@ -881,26 +881,27 @@ msgstr "" #: .\repair\js\views\strategy\setup-solutions.js:197 #: .\repair\js\views\study-area\keyflows.js:116 msgid "Success" -msgstr "" +msgstr "siker" #: .\repair\js\views\data-entry\bulk-upload.js:436 msgid "" "Warning: Uploading Area Levels removes all Areas in the casestudy as well. " "Do you wish to continue?" -msgstr "" +msgstr "Figyelem: A területi szint feltöltése törli az összes területet az esettanulmány területen is. " +"Biztosan folytatja?" #: .\repair\js\views\data-entry\bulk-upload.js:451 msgid "Response" -msgstr "" +msgstr "válasz" #: .\repair\js\views\data-entry\bulk-upload.js:489 #: .\repair\js\views\data-entry\bulk-upload.js:497 msgid "count" -msgstr "" +msgstr "számol" #: .\repair\js\views\data-entry\bulk-upload.js:499 msgid "defaults excluded" -msgstr "" +msgstr "alapértelmezett kizárva" #: .\repair\js\views\data-entry\edit-node.js:292 msgid "Lade..." @@ -908,11 +909,11 @@ msgstr "" #: .\repair\js\views\data-entry\edit-node.js:414 msgid "None" -msgstr "" +msgstr "egyik sem" #: .\repair\js\views\data-entry\edit-node.js:604 msgid "Select a material" -msgstr "" +msgstr "válasszon anyagot " #: .\repair\js\views\data-entry\edit-node.js:826 msgid "Nace Code" @@ -920,45 +921,47 @@ msgstr "" #: .\repair\js\views\data-entry\edit-node.js:830 msgid "Activity" -msgstr "" +msgstr "tevékenység" #: .\repair\js\views\data-entry\edit-node.js:832 msgid "City" -msgstr "" +msgstr "város" #: .\repair\js\views\data-entry\edit-node.js:833 msgid "Address" -msgstr "" +msgstr "cím" #: .\repair\js\views\data-entry\materials.js:83 msgid "read only" -msgstr "" +msgstr "csak olvasásra" #: .\repair\js\views\data-entry\materials.js:120 msgid "Material (directly used in flows / children in flows)" -msgstr "" +msgstr "Anyag (közvetlenül áramlásokban / áramlásokban használt gyermekeknél)" #: .\repair\js\views\status-quo\edit-indicator-flow.js:271 msgid "select an activity/group to display specific nodes" msgstr "" +"válasszon egy tevékenységet / csoportot, hogy megjelenítse az adott " +"csomópontokat" #: .\repair\js\views\status-quo\objectives.js:88 msgid "General" -msgstr "" +msgstr "általános" #: .\repair\js\views\status-quo\objectives.js:159 #: .\repair\js\views\status-quo\objectives.js:238 msgid "Challenge" -msgstr "" +msgstr "kihívás" #: .\repair\js\views\status-quo\objectives.js:160 #: .\repair\js\views\status-quo\objectives.js:284 msgid "Aim" -msgstr "" +msgstr "cél" #: .\repair\js\views\status-quo\objectives.js:245 msgid "Add Challenge" -msgstr "" +msgstr "kihívás hozzásadás" #: .\repair\js\views\status-quo\objectives.js:253 #: .\repair\js\views\status-quo\objectives.js:299 @@ -966,11 +969,11 @@ msgstr "" #: .\repair\js\views\study-area\stakeholders.js:234 #: .\repair\js\views\study-area\stakeholders.js:268 msgid "Description" -msgstr "" +msgstr "leírás" #: .\repair\js\views\status-quo\objectives.js:291 msgid "Add Aim" -msgstr "" +msgstr "Cél hozzáadása" #: .\repair\js\views\status-quo\setup-flow-assessment.js:198 #: .\repair\js\views\status-quo\setup-flows.js:125 @@ -978,19 +981,19 @@ msgstr "" #: .\repair\js\views\strategy\setup-solutions.js:197 #: .\repair\js\views\study-area\keyflows.js:116 msgid "Upload successful" -msgstr "" +msgstr "feltöltés sikeres" #: .\repair\js\views\status-quo\setup-flow-assessment.js:240 msgid "Do you want to delete the Indicator" -msgstr "" +msgstr "Valóban törölni kívánja az indikátort? " #: .\repair\js\views\status-quo\setup-flows.js:168 msgid "Do you want to delete the Filter" -msgstr "" +msgstr "Valóban törölni kívánja a szűrőt?" #: .\repair\js\views\status-quo\workshop-flow-assessment.js:496 msgid "Case Study
Region" -msgstr "" +msgstr "Esettanulmány
Régió" #: .\repair\js\views\status-quo\workshop-flow-assessment.js:496 msgid "Focus
Area" @@ -998,272 +1001,277 @@ msgstr "" #: .\repair\js\views\status-quo\workshop-flow-assessment.js:603 msgid "Region" -msgstr "" +msgstr "Régió" #: .\repair\js\views\status-quo\workshop-flow-assessment.js:661 #: .\repair\js\views\status-quo\workshop-flow-assessment.js:662 #: .\repair\js\views\targets\flow-targets.js:227 msgid "Focus Area" -msgstr "" +msgstr "Fókuszterület" #: .\repair\js\views\status-quo\workshop-flow-assessment.js:672 #: .\repair\js\views\status-quo\workshop-flow-assessment.js:673 #: .\repair\js\views\targets\flow-targets.js:227 msgid "Casestudy Region" -msgstr "" +msgstr "Esettanulmány Régió" #: .\repair\js\views\strategy\setup-solution-part.js:327 #: .\repair\js\views\strategy\setup-solution-part.js:344 #: .\repair\js\views\strategy\setup-solution-part.js:382 #: .\repair\js\views\strategy\setup-solutions-logic.js:218 msgid "Select" -msgstr "" +msgstr "válasszon" #: .\repair\js\views\strategy\setup-solutions-logic.js:181 msgid "successfully cloned" -msgstr "" +msgstr "sikeres klónozás" #: .\repair\js\views\strategy\setup-solutions-logic.js:264 msgid "clone item" -msgstr "" +msgstr "tétel klónozása" #: .\repair\js\views\strategy\setup-solutions-logic.js:405 msgid "GeoJSON needs attributes \"type\" and \"coordinates\"" -msgstr "" +msgstr "A GeoJSON-nak attribútumokra és \"koordinátákat\" van szüksége" #: .\repair\js\views\strategy\setup-solutions-logic.js:408 msgid "type has to be MultiPolygon or Polygon" -msgstr "" +msgstr "típusnak MultiPolygonnak vagy sokszögnek kell lennie" #: .\repair\js\views\strategy\setup-solutions.js:241 msgid "Do you want to delete the category?" -msgstr "" +msgstr "Törli a kategóriát?" #: .\repair\js\views\strategy\setup-solutions.js:327 msgid "Do you want to delete the selected solution?" -msgstr "" +msgstr "Valóban törli a kiválasztott megoldást?" #: .\repair\js\views\strategy\strategy.js:248 msgid "Do you really want to delete your solution?" -msgstr "" +msgstr "Valóban törli a megoldást?" #: .\repair\js\views\strategy\strategy.js:640 #: .\repair\js\views\strategy\strategy.js:646 msgid "possible implementation area" -msgstr "" +msgstr "lehetséges megvalósítási terület" #: .\repair\js\views\study-area\charts.js:76 msgid "The charts are not set up." -msgstr "" +msgstr "A diagramok nincsenek beállítva." #: .\repair\js\views\study-area\charts.js:354 msgid "Do you really want to delete the selected chart?" -msgstr "" +msgstr "Tényleg törölni szeretné a kiválasztott diagramot?" #: .\repair\js\views\study-area\charts.js:355 msgid "Do you really want to delete the selected category and all its charts?" msgstr "" +"Tényleg törölni szeretné a kiválasztott kategóriát és az összes diagramját?" #: .\repair\js\views\study-area\keyflows.js:92 msgid "Upload" -msgstr "" +msgstr "feltöltés" #: .\repair\js\views\study-area\setup-maps.js:293 msgid "Do you really want to delete the selected category and all its layers?" -msgstr "" +msgstr "Valóban törölni akarja a kiválasztott szereplőt és rétegeit? " #: .\repair\js\views\study-area\stakeholders.js:87 msgid "The stakeholders are not set up." -msgstr "" +msgstr "a szereplők nincsenek beállítva" #: .\repair\js\views\study-area\stakeholders.js:131 msgid "Stakeholder" -msgstr "" +msgstr "szereplő" #: .\repair\js\views\study-area\stakeholders.js:132 msgid "add stakeholder" -msgstr "" +msgstr "szereplő hozzáadása" #: .\repair\js\views\study-area\stakeholders.js:141 msgid "Remove category" -msgstr "" +msgstr "kategória eltávolítása" #: .\repair\js\views\study-area\stakeholders.js:152 msgid "Edit category" -msgstr "" +msgstr "kategória szerkesztése" #: .\repair\js\views\study-area\stakeholders.js:226 msgid "Add Stakeholder" -msgstr "" +msgstr "Szereplő hozzáadása" #: .\repair\js\views\study-area\stakeholders.js:258 msgid "Edit Stakeholder" -msgstr "" +msgstr "szereplő szerkesztése" #: .\repair\js\views\study-area\stakeholders.js:288 msgid "Do you want to delete the selected stakeholder?" -msgstr "" +msgstr "Valóban törli a kiválasztott szereplőt?" #: .\repair\js\views\study-area\stakeholders.js:304 msgid "Add Stakeholder Category" -msgstr "" +msgstr "Szereplőcsoport hozzáadása" #: .\repair\js\views\study-area\stakeholders.js:311 msgid "Do you really want to delete the stakeholder category?" -msgstr "" +msgstr "Tényleg törölni kívánja az érintettek kategóriáját?" #: .\repair\js\views\study-area\stakeholders.js:337 msgid "Edit Category" -msgstr "" +msgstr "Kategória szerkesztése" #: .\repair\js\views\study-area\workshop-maps.js:204 msgid "The map is not set up." -msgstr "" +msgstr "A térkép nincs beállítva." #: .\repair\js\views\study-area\workshop-maps.js:468 msgid "legend not found" -msgstr "" +msgstr "legenda nem található" #: .\repair\js\views\study-area\workshop-maps.js:508 msgid "Layer" -msgstr "" +msgstr "réteg" #: .\repair\js\views\targets\flow-targets.js:193 msgid "Remove target" -msgstr "" +msgstr "cél eltávolítása" #: .\repair\js\views\targets\flow-targets.js:300 msgid "" "No indicators available for the target definition. Please contact your " "workshop leader." msgstr "" +"Nincsenek elérhető mutatók a célmeghatározáshoz. Kérjük, forduljon a " +"műhelyvezetőhöz." #: .\repair\js\views\targets\flow-targets.js:337 msgid "" "Indicator is duplicate in this objective. Please remove or set another " "indicator." msgstr "" +"A mutató ebben a célkitűzésben ismétlődik. Kérjük, távolítson el vagy " +"állítson be egy másik mutatót." #: .\repair\js\views\targets\ranking-objectives.js:59 msgid "Ranking general objectives" -msgstr "" +msgstr "Az általános célok rangsorolása" #: .\repair\js\views\targets\ranking-objectives.js:61 msgid "Ranking objectives for the keyflow " -msgstr "" +msgstr "A kulcsáramlási célok rangsorolása " #: .\repair\settings.py:168 msgid "English" -msgstr "" +msgstr "angol " #: .\repair\settings.py:169 msgid "German" -msgstr "" +msgstr "német " #: .\repair\settings.py:170 msgid "Dutch" -msgstr "" +msgstr "holland " #: .\repair\settings.py:171 msgid "Polish" -msgstr "" +msgstr "lengyel " #: .\repair\settings.py:172 msgid "Hungarian" -msgstr "" +msgstr "magyar " #: .\repair\settings.py:173 msgid "Italian" -msgstr "" +msgstr "olasz " #: .\repair\static\bundles\prod\0.js:1 msgid "brush" -msgstr "" +msgstr "ecset" #: .\repair\static\bundles\prod\0.js:1 msgid "brushend" -msgstr "" +msgstr "az ecset bezárása" #: .\repair\static\bundles\prod\0.js:1 msgid "brushstart" -msgstr "" +msgstr "az ecset megnyitása" #: .\src\django-publications-bootstrap\publications_bootstrap\admin\publicationadmin.py:112 msgid "Mark selected %(verbose_name_plural)s as drafts" -msgstr "" +msgstr "A kiválasztott % (verbose_name_plural) megjelölése vázlatként" #: .\src\django-publications-bootstrap\publications_bootstrap\admin\publicationadmin.py:117 msgid "Mark selected %(verbose_name_plural)s as submitted" -msgstr "" +msgstr "A kiválasztott % (verbose_name_plural) megjelölése benyújtottként" #: .\src\django-publications-bootstrap\publications_bootstrap\admin\publicationadmin.py:122 msgid "Mark selected %(verbose_name_plural)s as accepted" -msgstr "" +msgstr "A kiválasztott % (verbose_name_plural) megjelölése elfogadottként" #: .\src\django-publications-bootstrap\publications_bootstrap\admin\publicationadmin.py:127 msgid "Mark selected %(verbose_name_plural)s as published" -msgstr "" +msgstr "A kiválasztott % (verbose_name_plural) megjelölése közzétettként" #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:41 msgid "January" -msgstr "" +msgstr "január " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:42 msgid "February" -msgstr "" +msgstr "február " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:43 msgid "March" -msgstr "" +msgstr "március " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:44 msgid "April" -msgstr "" +msgstr "Április " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:45 msgid "May" -msgstr "" +msgstr "május " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:46 msgid "June" -msgstr "" +msgstr "június " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:47 msgid "July" -msgstr "" +msgstr "július " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:48 msgid "August" -msgstr "" +msgstr "augusztus " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:49 msgid "September" -msgstr "" +msgstr "szeptember " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:50 msgid "October" -msgstr "" +msgstr "október " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:51 msgid "November" -msgstr "" +msgstr "november " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:52 msgid "December" -msgstr "" +msgstr "december " #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:68 msgid "draft" -msgstr "" +msgstr "vázlat" #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:69 msgid "submitted" -msgstr "" +msgstr "benyújtva" #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:70 msgid "accepted" -msgstr "" +msgstr "elfogadva" #: .\src\django-publications-bootstrap\publications_bootstrap\models\publication.py:71 msgid "published" -msgstr "" +msgstr "közzétett" diff --git a/repair/settings.py b/repair/settings.py index aaa54e5a6..30fb85442 100644 --- a/repair/settings.py +++ b/repair/settings.py @@ -230,9 +230,9 @@ 'django.contrib.staticfiles.finders.AppDirectoriesFinder' ) -FIXTURE_DIRS = ( +FIXTURE_DIRS = [ os.path.join(PROJECT_DIR, "fixtures"), -) +] LOGGING = { 'version': 1, diff --git a/repair/settings4tests.py b/repair/settings4tests.py index a099ed47d..1f16959f3 100644 --- a/repair/settings4tests.py +++ b/repair/settings4tests.py @@ -51,3 +51,5 @@ 'STATS_FILE': os.path.join(PROJECT_DIR, 'webpack-stats-dev.json'), } } + +FIXTURE_DIRS.append(os.path.join(PROJECT_DIR, "graph_fixtures"),) diff --git a/repair/settings_dev_vagrant.py b/repair/settings_dev_vagrant.py index 6d1810f3b..9a57c8443 100644 --- a/repair/settings_dev_vagrant.py +++ b/repair/settings_dev_vagrant.py @@ -25,3 +25,5 @@ 'STATS_FILE': os.path.join(PROJECT_DIR, 'webpack-stats-dev.json'), } } + +FIXTURE_DIRS.append(os.path.join(PROJECT_DIR, "graph_fixtures"),) diff --git a/repair/static/css/base.less b/repair/static/css/base.less index 333f9b994..a5560e506 100644 --- a/repair/static/css/base.less +++ b/repair/static/css/base.less @@ -491,6 +491,7 @@ tr.popunder > td{ .modal{ top: 80px; + overflow: auto; } .btn { diff --git a/repair/static/css/strategy.css b/repair/static/css/strategy.css new file mode 100644 index 000000000..0bce3c212 --- /dev/null +++ b/repair/static/css/strategy.css @@ -0,0 +1,81 @@ +.scheme-preview{ + width: 30%; + cursor: pointer; + float: left; + border: 1px solid grey; + white-space: initial; + padding: 10px; + border-radius: 5px; + box-shadow: 0px 1px 6px 0px rgba(0, 0, 0, 0.2); + margin: 5px; +} + +.scheme-preview:hover, .scheme-preview.selected{ + background: #aad400; + color:white; +} + +.scheme-preview > img{ + width: 100%; + background: white; + border-radius: 5px; +} + +.scheme-preview > label{ + text-align: center; + width: 100%; + cursor: pointer; +} + +#selected-scheme-image { + max-width: 100%; + max-height: 500px; +} + +.part-table{ + table-layout: fixed; + max-width: 100%; + width: 100%; +} + +.part-table .btn-group, .part-table select{ + max-width: 100%; +} + +#affected-flows .btn-group, .affected-flows select{ + max-width: 150px; +} + +.part-table th{ + padding: 5px; + font-weight: normal; +} + +.part-table tr{ + border-bottom: 1px solid lightgrey; +} + +.part-table td:first-child{ + width:10%; +} + +.part-table tr:last-child{ + border-bottom: none; +} + +#scheme-preview img, #affected-flows-tab img{ + max-width: 100%; + cursor: pointer; + border: 1px solid darkgrey; +} + +/*white background in fullscreen*/ +.viewer-backdrop img { + background-color: white; +} + +.count-label { + float: left; + padding: 3px; + margin-right: 20px; +} diff --git a/repair/static/css/study-area.css b/repair/static/css/study-area.css index da9dcfb7f..787a7230a 100644 --- a/repair/static/css/study-area.css +++ b/repair/static/css/study-area.css @@ -9,6 +9,7 @@ width: 100%; height: 100%; left: 0px; + background-color: white; /*padding-left: 60px;*/ } #base-map.workshop { diff --git a/repair/static/img/schemes/affected-example.png b/repair/static/img/schemes/affected-example.png new file mode 100644 index 000000000..3943acd3a Binary files /dev/null and b/repair/static/img/schemes/affected-example.png differ diff --git a/repair/static/img/schemes/affected.png b/repair/static/img/schemes/affected.png new file mode 100644 index 000000000..2b9cc343b Binary files /dev/null and b/repair/static/img/schemes/affected.png differ diff --git a/repair/static/img/schemes/append-example.png b/repair/static/img/schemes/append-example.png new file mode 100644 index 000000000..8ed64f904 Binary files /dev/null and b/repair/static/img/schemes/append-example.png differ diff --git a/repair/static/img/schemes/append.png b/repair/static/img/schemes/append.png new file mode 100644 index 000000000..cc07c4b2a Binary files /dev/null and b/repair/static/img/schemes/append.png differ diff --git a/repair/static/img/schemes/legend.png b/repair/static/img/schemes/legend.png new file mode 100644 index 000000000..87715254d Binary files /dev/null and b/repair/static/img/schemes/legend.png differ diff --git a/repair/static/img/schemes/modification-example.png b/repair/static/img/schemes/modification-example.png new file mode 100644 index 000000000..176decb1d Binary files /dev/null and b/repair/static/img/schemes/modification-example.png differ diff --git a/repair/static/img/schemes/modification.png b/repair/static/img/schemes/modification.png new file mode 100644 index 000000000..12858ff80 Binary files /dev/null and b/repair/static/img/schemes/modification.png differ diff --git a/repair/static/img/schemes/new-example.png b/repair/static/img/schemes/new-example.png new file mode 100644 index 000000000..94fc4f205 Binary files /dev/null and b/repair/static/img/schemes/new-example.png differ diff --git a/repair/static/img/schemes/new.png b/repair/static/img/schemes/new.png new file mode 100644 index 000000000..75ef73c13 Binary files /dev/null and b/repair/static/img/schemes/new.png differ diff --git a/repair/static/img/schemes/prepend-example.png b/repair/static/img/schemes/prepend-example.png new file mode 100644 index 000000000..13a7d5962 Binary files /dev/null and b/repair/static/img/schemes/prepend-example.png differ diff --git a/repair/static/img/schemes/prepend.png b/repair/static/img/schemes/prepend.png new file mode 100644 index 000000000..74ecfd93d Binary files /dev/null and b/repair/static/img/schemes/prepend.png differ diff --git a/repair/static/img/schemes/shift-destination-example.png b/repair/static/img/schemes/shift-destination-example.png new file mode 100644 index 000000000..292d20cb5 Binary files /dev/null and b/repair/static/img/schemes/shift-destination-example.png differ diff --git a/repair/static/img/schemes/shift-destination.png b/repair/static/img/schemes/shift-destination.png new file mode 100644 index 000000000..feee81b09 Binary files /dev/null and b/repair/static/img/schemes/shift-destination.png differ diff --git a/repair/static/img/schemes/shift-origin-example.png b/repair/static/img/schemes/shift-origin-example.png new file mode 100644 index 000000000..6915bb0c7 Binary files /dev/null and b/repair/static/img/schemes/shift-origin-example.png differ diff --git a/repair/static/img/schemes/shift-origin.png b/repair/static/img/schemes/shift-origin.png new file mode 100644 index 000000000..c4b37fe1f Binary files /dev/null and b/repair/static/img/schemes/shift-origin.png differ diff --git a/repair/templates/base.html b/repair/templates/base.html index b04a8a1da..7b2fdb4c4 100644 --- a/repair/templates/base.html +++ b/repair/templates/base.html @@ -52,7 +52,7 @@ - {% if setup_mode_permitted %} @@ -87,11 +86,10 @@ - {% endif %} @@ -155,7 +153,6 @@ {% endfor %} - diff --git a/repair/templates/casestudy-missing.html b/repair/templates/casestudy-missing.html index 683e8191d..78ee84908 100644 --- a/repair/templates/casestudy-missing.html +++ b/repair/templates/casestudy-missing.html @@ -4,7 +4,7 @@ {% block content %} {% render_bundle 'Base' %} -

{% trans "Please select a Casestudy first!" %}

+

{% trans "Please select a case study first!" %}

{% csrf_token %} @@ -15,4 +15,4 @@

{% trans "Please select a Casestudy first!" %}

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/repair/templates/common.html b/repair/templates/common.html index aacd463c0..8db4099fa 100644 --- a/repair/templates/common.html +++ b/repair/templates/common.html @@ -4,8 +4,8 @@ - - + + diff --git a/repair/templates/conclusions/setup.html b/repair/templates/conclusions/setup.html index 0410cdbeb..0443cee74 100644 --- a/repair/templates/conclusions/setup.html +++ b/repair/templates/conclusions/setup.html @@ -72,28 +72,7 @@

{% trans "Organizing Sections" %}

({% trans 'e.g. "legal framework", {% render_bundle 'Recommendations' %} {% endif %} - +{% include "statusquo/sustainability.html" %} + {% endblock %} diff --git a/repair/templates/conclusions/workshop.html b/repair/templates/conclusions/workshop.html index 318e5849c..a67b5f268 100644 --- a/repair/templates/conclusions/workshop.html +++ b/repair/templates/conclusions/workshop.html @@ -11,7 +11,7 @@