]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.network/src/org/simantics/district/network/DistrictNetworkUtil.java
Moved compareNatural to TechTypeUtils
[simantics/district.git] / org.simantics.district.network / src / org / simantics / district / network / DistrictNetworkUtil.java
1 package org.simantics.district.network;
2
3 import java.awt.geom.Rectangle2D;
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.Collections;
7 import java.util.Comparator;
8 import java.util.HashSet;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.Set;
12 import java.util.stream.Collectors;
13 import java.util.stream.Stream;
14
15 import org.simantics.Simantics;
16 import org.simantics.databoard.Bindings;
17 import org.simantics.datatypes.literal.RGB;
18 import org.simantics.datatypes.literal.RGB.Integer;
19 import org.simantics.db.ReadGraph;
20 import org.simantics.db.RequestProcessor;
21 import org.simantics.db.Resource;
22 import org.simantics.db.WriteGraph;
23 import org.simantics.db.common.NamedResource;
24 import org.simantics.db.common.procedure.adapter.TransientCacheListener;
25 import org.simantics.db.common.request.BinaryRead;
26 import org.simantics.db.common.request.IndexRoot;
27 import org.simantics.db.common.request.ObjectsWithType;
28 import org.simantics.db.common.request.PossibleChild;
29 import org.simantics.db.common.request.PossibleIndexRoot;
30 import org.simantics.db.common.request.ResourceRead;
31 import org.simantics.db.common.utils.OrderedSetUtils;
32 import org.simantics.db.exception.BindingException;
33 import org.simantics.db.exception.DatabaseException;
34 import org.simantics.db.exception.ManyObjectsForFunctionalRelationException;
35 import org.simantics.db.exception.ServiceException;
36 import org.simantics.db.indexing.IndexUtils;
37 import org.simantics.db.layer0.QueryIndexUtils;
38 import org.simantics.db.layer0.request.PossibleActiveModel;
39 import org.simantics.db.layer0.request.PossibleVariable;
40 import org.simantics.db.layer0.variable.Variable;
41 import org.simantics.diagram.stubs.DiagramResource;
42 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
43 import org.simantics.diagram.synchronization.graph.layer.GraphLayer;
44 import org.simantics.diagram.synchronization.graph.layer.IGraphLayerUtil;
45 import org.simantics.district.network.ontology.DistrictNetworkResource;
46 import org.simantics.layer0.Layer0;
47 import org.simantics.maps.elevation.server.SingletonTiffTileInterface;
48 import org.simantics.maps.elevation.server.prefs.MapsElevationServerPreferences;
49 import org.simantics.modeling.ModelingResources;
50 import org.simantics.modeling.adapters.NewCompositeActionFactory;
51 import org.simantics.operation.Layer0X;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 import com.vividsolutions.jts.geom.Envelope;
56 import com.vividsolutions.jts.index.quadtree.Quadtree;
57
58 public class DistrictNetworkUtil {
59
60     private static final Logger LOGGER = LoggerFactory.getLogger(DistrictNetworkUtil.class);
61
62     public static Resource createEdge(WriteGraph graph, Resource composite, double[] detailedGeometryCoords) throws DatabaseException {
63         return createEdge(graph, composite, graph.getPossibleObject(composite, DistrictNetworkResource.getInstance(graph).EdgeDefaultMapping), detailedGeometryCoords);
64     }
65
66     public static Resource createEdge(WriteGraph graph, Resource composite, Resource mapping, double[] detailedGeometryCoords) throws DatabaseException {
67         Layer0 L0 = Layer0.getInstance(graph);
68         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
69         if (mapping == null) {
70             mapping = graph.getSingleObject(composite, DN.EdgeDefaultMapping);
71         }
72         
73         Resource edge = graph.newResource();
74         graph.claim(edge, L0.InstanceOf, DN.Edge);
75         
76         graph.claim(edge, DN.HasMapping, null, mapping);
77         
78         OrderedSetUtils.addFirst(graph, composite, edge);
79         graph.claim(composite, L0.ConsistsOf, L0.PartOf, edge);
80         
81         claimFreshElementName(graph, composite, edge);
82         
83         // We need to put GraphLayer to newLayers so...
84         for (Resource layer : graph.getObjects(composite, DiagramResource.getInstance(graph).HasLayer)) {
85             IGraphLayerUtil layerUtil = graph.adapt(graph.getSingleObject(layer, Layer0.getInstance(graph).InstanceOf), IGraphLayerUtil.class);
86             
87             GraphLayer gl = layerUtil.loadLayer(graph, layer);
88             gl.forEachTag(tag -> {
89                 DiagramGraphUtil.tag(graph, edge, tag, true);
90             });
91         }
92         
93         // add detailed geometry (if any)
94         graph.claimLiteral(edge, DN.Edge_HasGeometry, detailedGeometryCoords, Bindings.DOUBLE_ARRAY);
95         return edge;
96     }
97
98     /**
99      * @param graph
100      * @param composite
101      * @param coords
102      * @param elevation Double.MAX_VALUE to fetch elevation from elevation server (if enabled and has data)
103      * @return
104      * @throws DatabaseException
105      */
106     public static Resource createVertex(WriteGraph graph, Resource composite, double[] coords, double elevation) throws DatabaseException {
107         Resource defaultVertexMapping = graph.getPossibleObject(composite, DistrictNetworkResource.getInstance(graph).VertexDefaultMapping);
108         return createVertex(graph, composite, coords, elevation, defaultVertexMapping);
109     }
110
111     /**
112      * @param graph
113      * @param composite
114      * @param coords
115      * @param elevation Double.MAX_VALUE to fetch elevation from elevation server (if enabled and has data)
116      * @param mapping
117      * @return
118      * @throws DatabaseException
119      */
120     public static Resource createVertex(WriteGraph graph, Resource composite, double[] coords, double elevation, Resource mapping) throws DatabaseException {
121         // Double.MAX_VALUE is our secret to lookup elevation from elevation server
122         if (elevation == Double.MAX_VALUE) {
123             // ok, resolve from server or default to 0
124             if (MapsElevationServerPreferences.useElevationServer()) {
125                 // ok! we use new elevation API to resolve possible elevations for the starting points
126                 try {
127                     elevation = SingletonTiffTileInterface.lookup(coords[1], coords[0]).doubleValue();
128                 } catch (Exception ee) {
129                     LOGGER.error("Could not get elevation from tiff interface", ee);
130                 }
131             } else {
132                 elevation = 0;
133             }
134         }
135         
136         Layer0 L0 = Layer0.getInstance(graph);
137         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
138         DiagramResource DIA = DiagramResource.getInstance(graph);
139         Resource vertex = graph.newResource();
140         graph.claim(vertex, L0.InstanceOf, DN.Vertex);
141         graph.claimLiteral(vertex, DIA.HasLocation, coords);
142         graph.claimLiteral(vertex, DN.Vertex_HasElevation, elevation, Bindings.DOUBLE);
143         
144         graph.claim(vertex, DN.HasMapping, null, mapping);
145         
146         OrderedSetUtils.add(graph, composite, vertex);
147         graph.claim(composite, L0.ConsistsOf, L0.PartOf, vertex);
148         
149         claimFreshElementName(graph, composite, vertex);
150         
151         // We need to put GraphLayer to newLayers so...
152         for (Resource layer : graph.getObjects(composite, DiagramResource.getInstance(graph).HasLayer)) {
153             IGraphLayerUtil layerUtil = graph.adapt(graph.getSingleObject(layer, Layer0.getInstance(graph).InstanceOf), IGraphLayerUtil.class);
154             
155             GraphLayer gl = layerUtil.loadLayer(graph, layer);
156             gl.forEachTag(tag -> {
157                 DiagramGraphUtil.tag(graph, vertex, tag, true);
158             });
159         }
160         
161         return vertex;
162     }
163     
164     public static Resource joinVertices(WriteGraph graph, Collection<Resource> vertices) throws DatabaseException {
165         if (vertices.isEmpty())
166             throw new IllegalArgumentException("vertices-collection should not be empty for joining vertices!");
167         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
168         Iterator<Resource> verticeIterator = vertices.iterator();
169         Resource master = verticeIterator.next();
170         while (verticeIterator.hasNext()) {
171             Resource slave = verticeIterator.next();
172             Resource composite = graph.getSingleObject(slave, Layer0.getInstance(graph).PartOf);
173             Collection<Resource> startVertexEdges = graph.getObjects(slave, DN.HasStartVertex_Inverse);
174             for (Resource startVertexEdge : startVertexEdges) {
175                 graph.deny(startVertexEdge, DN.HasStartVertex);
176                 graph.claim(startVertexEdge, DN.HasStartVertex, master);
177             }
178             Collection<Resource> endVertexEdges = graph.getObjects(slave, DN.HasEndVertex_Inverse);
179             for (Resource endVertexEdge : endVertexEdges) {
180                 graph.deny(endVertexEdge, DN.HasEndVertex);
181                 graph.claim(endVertexEdge, DN.HasEndVertex, master);
182             }
183             OrderedSetUtils.remove(graph, composite, slave);
184             // Remove ConsistsOf statement
185             graph.deny(composite, Layer0.getInstance(graph).ConsistsOf, slave);
186         }
187         return master;
188     }
189     
190     public static double calculateDistance(ReadGraph graph, Resource startVertex, Resource endVertex) throws DatabaseException {
191         Layer0 L0 = Layer0.getInstance(graph);
192         Resource startComposite = graph.getSingleObject(startVertex, L0.PartOf);
193         Resource endComposite = graph.getSingleObject(endVertex, L0.PartOf);
194         if (!startComposite.equalsResource(endComposite)) {
195             throw new DatabaseException("Can not calculate distance between vertices on different composites! " + startVertex + " -> " + endVertex);
196         }
197         Resource crs = graph.getSingleObject(startComposite, DistrictNetworkResource.getInstance(graph).HasSpatialRefSystem);
198         
199         CRS crsClass = graph.adapt(crs, CRS.class);
200         
201         double[] startCoords = graph.getRelatedValue2(startVertex, DiagramResource.getInstance(graph).HasLocation, Bindings.DOUBLE_ARRAY);
202         double[] endCoords = graph.getRelatedValue2(endVertex, DiagramResource.getInstance(graph).HasLocation, Bindings.DOUBLE_ARRAY);
203         
204         return crsClass.calculateDistance(startCoords, endCoords);
205     }
206     
207     public static final String claimFreshElementName(WriteGraph graph, Resource diagram, Resource element) throws DatabaseException {
208         Layer0 L0 = Layer0.getInstance(graph);
209         DiagramResource DIA = DiagramResource.getInstance(graph);
210         // Get name prefix from diagram
211         String namePrefix = graph.getPossibleRelatedValue2(diagram, Layer0X.getInstance(graph).HasGeneratedNamePrefix);
212         if (namePrefix == null)
213             namePrefix = "";
214         // Give running name to element and increment the counter attached to the diagram.
215         Long l = graph.getPossibleRelatedValue(diagram, DIA.HasModCount, Bindings.LONG);
216         if (l == null)
217             l = Long.valueOf(0L);
218         String name = namePrefix + l.toString();
219         graph.claimLiteral(element, L0.HasName, name, Bindings.STRING);
220         graph.claimLiteral(diagram, DIA.HasModCount, ++l, Bindings.LONG);
221         return name;
222     }
223
224     public static Resource getDiagramElement(ReadGraph graph, Resource component) throws DatabaseException {
225         if (component == null)
226             return null;
227         DiagramResource DIA = DiagramResource.getInstance(graph);
228         if (graph.isInstanceOf(component, DIA.Element))
229             return component;
230         ModelingResources MOD = ModelingResources.getInstance(graph);
231         Resource element = graph.getPossibleObject(component, MOD.ComponentToElement);
232         return element != null && graph.isInstanceOf(element, DIA.Element) ? element : null;
233     }
234
235     public static Resource getMappedElement(ReadGraph graph, Resource element) throws DatabaseException {
236         if (element == null)
237             return null;
238         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
239         return graph.getPossibleObject(element, DN.MappedComponent);
240     }
241
242     public static Resource getMappedComponent(ReadGraph graph, Resource element) throws DatabaseException {
243         if (element == null)
244             return null;
245         Resource mappedElement = getMappedElement(graph, element);
246         if (mappedElement == null)
247             return null;
248         ModelingResources MOD = ModelingResources.getInstance(graph);
249         return graph.getPossibleObject(mappedElement, MOD.ElementToComponent);
250     }
251     
252     public static Resource getMappedComponentCached(ReadGraph graph, Resource vertex) throws DatabaseException {
253         return graph.syncRequest(new MappedComponentRequest(vertex), TransientCacheListener.instance());
254     }
255
256     public static Resource getMappedDNElement(ReadGraph graph, Resource element) throws DatabaseException {
257         if (element == null)
258             return null;
259         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
260         return graph.getPossibleObject(element, DN.MappedFromElement);
261     }
262
263     public static Variable toMappedConfigurationModule(ReadGraph graph, Resource input) throws DatabaseException {
264         if (input == null)
265             return null;
266
267         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
268         if (graph.isInstanceOf(input, DN.Element)) {
269             Resource mappedElement = getMappedElement(graph, input);
270             if (mappedElement == null)
271                 return null;
272
273             ModelingResources MOD = ModelingResources.getInstance(graph);
274             Resource mappedComponent = graph.getPossibleObject(mappedElement, MOD.ElementToComponent);
275             if (mappedComponent == null)
276                 return null;
277
278             return graph.syncRequest(new PossibleVariable(mappedComponent));
279         }
280         return null;
281     }
282
283     public static void toggleDrawMap(WriteGraph graph, Resource diagram) throws ManyObjectsForFunctionalRelationException, BindingException, ServiceException {
284         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
285         Boolean current = graph.getPossibleRelatedValue(diagram, DN.Diagram_drawMapEnabled, Bindings.BOOLEAN);
286         if (current == null)
287             current = true;
288         graph.claimLiteral(diagram, DN.Diagram_drawMapEnabled, !current, Bindings.BOOLEAN);
289     }
290
291     public static Boolean drawMapEnabled(ReadGraph graph, Resource diagram) throws ManyObjectsForFunctionalRelationException, BindingException, ServiceException {
292         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
293         Boolean current = graph.getPossibleRelatedValue(diagram, DN.Diagram_drawMapEnabled, Bindings.BOOLEAN);
294         return current != null ? current : true;
295     }
296
297     public static void changeMapBackgroundColor(WriteGraph graph, Resource diagram, Integer integer) throws DatabaseException {
298         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
299         graph.claimLiteral(diagram, DN.Diagram_backgroundColor, integer, Bindings.getBindingUnchecked(RGB.Integer.class));
300     }
301     
302     public static Boolean trackChangesEnabled(ReadGraph graph, Resource diagram) throws DatabaseException {
303         if (diagram != null && graph.hasStatement(diagram)) {
304             return Boolean.TRUE.equals(graph.getPossibleRelatedValue(diagram,
305                 DistrictNetworkResource.getInstance(graph).Diagram_trackChangesEnabled));
306         } else {
307             return false;
308         }
309     }
310
311     public static RGB.Integer backgroundColor(ReadGraph graph, Resource diagram) throws DatabaseException {
312         return graph.getPossibleRelatedValue(diagram,
313                 DistrictNetworkResource.getInstance(graph).Diagram_backgroundColor,
314                 Bindings.getBindingUnchecked(RGB.Integer.class));
315     }
316     
317     public static Resource createNetworkDiagram(WriteGraph graph, Resource target, Resource compositeType, String defaultName, Resource defaultEdgeMapping, Resource defaultVertexMapping, Resource rightClickVertexMapping, Resource leftClickVertexMapping, Resource crs) throws DatabaseException {
318         Resource composite = NewCompositeActionFactory.createComposite(graph, target, defaultName, compositeType);
319
320         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
321         Resource diagram = graph.getSingleObject(composite, ModelingResources.getInstance(graph).CompositeToDiagram);
322         graph.claim(diagram, DN.EdgeDefaultMapping, defaultEdgeMapping);
323         graph.claim(diagram, DN.VertexDefaultMapping, defaultVertexMapping);
324         graph.claim(diagram, DN.RightClickDefaultMapping, rightClickVertexMapping);
325         graph.claim(diagram, DN.LeftClickDefaultMapping, leftClickVertexMapping);
326         graph.claim(diagram, DN.HasSpatialRefSystem, crs);
327         
328         // Generated name prefix from composite name
329         String compositeName = graph.getRelatedValue2(composite, Layer0.getInstance(graph).HasName, Bindings.STRING);
330         graph.claimLiteral(diagram, Layer0X.getInstance(graph).HasGeneratedNamePrefix, "N" + compositeName.substring(compositeName.length() - 1, compositeName.length()));
331         
332         return composite;
333     }
334
335     public static final class MappedComponentRequest extends ResourceRead<Resource> {
336         public MappedComponentRequest(Resource element) {
337             super(element);
338         }
339
340         @Override
341         public Resource perform(ReadGraph graph) throws DatabaseException {
342             return getMappedComponent(graph, resource);
343         }
344     }
345
346     public static class ResourceVertex {
347         
348         public final boolean isConsumer;
349         public final Resource vertex;
350         public final double[] coords;
351         
352         public ResourceVertex(Resource vertex, double[] coords, boolean isConsumer) {
353             this.vertex = vertex;
354             this.coords = coords;
355             this.isConsumer = isConsumer;
356         }
357     }
358     
359     public static void changeMappingType(WriteGraph graph, Resource newMapping, List<Resource> elements) throws DatabaseException {
360         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
361         for (Resource element : elements) {
362             graph.deny(element, DN.HasMapping);
363             graph.claim(element, DN.HasMapping, newMapping);
364         }
365     }
366
367     public static Stream<Resource> findDNElementsById(ReadGraph graph, Resource context, String idToFind) throws DatabaseException {
368         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); 
369         return IndexUtils.findByType(graph,
370             graph.syncRequest(new IndexRoot(context)),
371             DN.Element
372         ).stream().filter(element -> {
373             try {
374                 String id = graph.getPossibleRelatedValue(element, DN.HasId, Bindings.STRING);
375                 return id != null && id.contains(idToFind);
376             } catch (DatabaseException e) {
377                 LOGGER.error("Could not read id for element {]", element, e);
378                 return false;
379             }
380         });
381     }
382     
383     public static Resource findDNElementById(ReadGraph graph, Resource context, String idToFind) throws DatabaseException {
384         List<Resource> elements = findDNElementsById(graph, context, idToFind).collect(Collectors.toList());
385         if (elements.size() == 1) {
386             return elements.iterator().next();
387         }
388         return null;
389     }
390     
391     public static List<Resource> findDNElementByXYCoordinates(ReadGraph graph, Resource context, double lat, double lon, double padding) throws DatabaseException {
392         DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
393         DiagramResource DIA = DiagramResource.getInstance(graph);
394         List<Resource> results = new ArrayList<>();
395         Collection<Resource> vertices = IndexUtils.findByType(graph, graph.syncRequest(new IndexRoot(context)), DN.Vertex);
396         Rectangle2D rect = new Rectangle2D.Double(lat, lon, padding, padding);
397         for (Resource vertex : vertices) {
398             double[] location = graph.getRelatedValue(vertex, DIA.HasLocation, Bindings.DOUBLE_ARRAY);
399             if (rect.contains(location[0], location[1])) {
400                 results.add(vertex);
401             }
402         }
403         return results;
404     }
405
406     public static List<ResourceVertex> nearbyResourceVertices(ReadGraph graph, Resource diagramResource, Resource vertex, Double padding) throws DatabaseException {
407         double halfPadding = padding / 2;
408         
409         Quadtree existingVertices = graph.syncRequest(new ExistingVerticesRead(diagramResource, halfPadding), TransientCacheListener.instance());
410         double[] coords = graph.getRelatedValue(vertex, DiagramResource.getInstance(graph).HasLocation, Bindings.DOUBLE_ARRAY);
411         double x1 = coords[0] - halfPadding;
412         double y1= coords[1] - halfPadding;
413         double x2 = coords[0] + halfPadding;
414         double y2= coords[1] + halfPadding;
415         Envelope e = new Envelope(x1, x2, y1, y2);
416         
417         List<?> result = existingVertices.query(e);
418         @SuppressWarnings("unchecked")
419         List<ResourceVertex> vertices = (List<ResourceVertex>) result;
420         
421         Rectangle2D vertexRectangle = new Rectangle2D.Double(coords[0] - halfPadding, coords[1] - halfPadding, padding, padding);
422         
423         // let's sort by distance
424         List<ResourceVertex> sortedVertices = vertices.stream().filter(rv -> {
425             if (rv.vertex.equals(vertex))
426                 return false;
427             
428             Rectangle2D nearbyRectangle = new Rectangle2D.Double(rv.coords[0] - halfPadding, rv.coords[1] - halfPadding, padding, padding);
429             return vertexRectangle.intersects(nearbyRectangle);
430         }).sorted((o1, o2) -> {
431             double disto1 = Math.sqrt((Math.pow(coords[0] - o1.coords[0], 2) + (Math.pow(coords[1] - o1.coords[1], 2))));
432             double disto2 = Math.sqrt((Math.pow(coords[0] - o2.coords[0], 2) + (Math.pow(coords[1] - o2.coords[1], 2))));
433             
434             if (o1.vertex.getResourceId() == 2554883) {
435                 System.err.println("here we are");
436             }
437             
438             return Double.compare(disto1, disto2);
439         }).collect(Collectors.toList());
440         return sortedVertices;
441     }
442
443     public static List<Resource> nearbyVertices(ReadGraph graph, Resource vertex, double padding) throws DatabaseException {
444         Resource diagramResource = graph.getSingleObject(vertex, Layer0.getInstance(graph).PartOf);
445         return nearbyResourceVertices(graph, diagramResource, vertex, padding)
446                 .stream()
447                 .map(rv -> rv.vertex)
448                 .collect(Collectors.toList());
449     }
450
451     public static Quadtree existingVertices(Resource diagramResource, Double padding) throws DatabaseException {
452         Quadtree vv = Simantics.getSession().syncRequest(new ExistingVerticesRead(diagramResource, padding));
453         return vv;
454     }
455
456     public static class ExistingVerticesRead extends BinaryRead<Resource, Double, Quadtree> {
457
458         public ExistingVerticesRead(Resource diagramResource, Double padding) {
459             super(diagramResource, padding);
460         }
461
462         @Override
463         public Quadtree perform(ReadGraph graph) throws DatabaseException {
464             Collection<Resource> vertices = graph.syncRequest(new ObjectsWithType(parameter, Layer0.getInstance(graph).ConsistsOf, DistrictNetworkResource.getInstance(graph).Vertex));
465             Quadtree vv = new Quadtree();
466             for (Resource vertex : vertices) {
467                 double[] coords = graph.getRelatedValue2(vertex, DiagramResource.getInstance(graph).HasLocation, Bindings.DOUBLE_ARRAY);
468                 double x1 = coords[0] - parameter2;
469                 double y1= coords[1] - parameter2;
470                 double x2 = coords[0] + parameter2;
471                 double y2= coords[1] + parameter2;
472                 Envelope e = new Envelope(x1, x2, y1, y2);
473                 vv.insert(e, new ResourceVertex(vertex, coords, true));
474             }
475             return vv;
476         }
477     }
478
479     public static class DistrictComponentListRequest extends ResourceRead<List<NamedResource>> {
480         protected DistrictComponentListRequest(Resource model) {
481             super(model);
482         }
483
484         @Override
485         public List<NamedResource> perform(ReadGraph graph) throws DatabaseException {
486             Layer0 L0 = Layer0.getInstance(graph);
487             DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
488             
489             Resource model = this.resource;
490             
491             Set<Resource> componentTypes = new HashSet<>();
492             Collection<Resource> mappings = QueryIndexUtils.searchByType(graph, model, DN.Mapping_Base);
493             for (Resource r : mappings) {
494                 String componentType = graph.getPossibleRelatedValue2(r, DN.Mapping_ComponentType);
495                 if (componentType != null) {
496                     Resource root = graph.syncRequest(new PossibleIndexRoot(r));
497                     if (root != null) {
498                         Resource type = graph.syncRequest(new PossibleChild(root, componentType));
499                         if (type != null)
500                             componentTypes.add(type);
501                     }
502                 }
503             }
504             
505             List<NamedResource> result = new ArrayList<NamedResource>(componentTypes.size());
506             for (Resource r : componentTypes) {
507                 String name = graph.getPossibleRelatedValue(r, L0.HasName);
508                 result.add(new NamedResource(name, r));
509             }
510             
511             result.sort(Comparator.comparing(NamedResource::getName));
512             return result;
513         }
514     }
515     
516     public static List<NamedResource> getDistrictComponents() throws DatabaseException {
517         return getDistrictComponents(Simantics.getSession());
518     }
519
520     public static List<NamedResource> getDistrictComponents(RequestProcessor session) throws DatabaseException {
521         Resource model = session.syncRequest(new PossibleActiveModel(Simantics.getProjectResource()));
522         if (model == null)
523             return Collections.emptyList();
524         
525         return session.syncRequest(new DistrictComponentListRequest(model), TransientCacheListener.instance());
526     }
527
528 }