source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/relation/sort/RelationSorter.java@ 17867

Last change on this file since 17867 was 17867, checked in by simon04, 3 years ago

see #17177 - fix @since xxx

  • Property svn:eol-style set to native
File size: 9.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.relation.sort;
3
4import java.util.ArrayList;
5import java.util.Arrays;
6import java.util.Collection;
7import java.util.Comparator;
8import java.util.HashMap;
9import java.util.LinkedHashMap;
10import java.util.LinkedList;
11import java.util.List;
12import java.util.Map;
13import java.util.Map.Entry;
14import java.util.Objects;
15import java.util.stream.Collectors;
16
17import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
18import org.openstreetmap.josm.data.osm.IPrimitive;
19import org.openstreetmap.josm.data.osm.IRelationMember;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Relation;
22import org.openstreetmap.josm.data.osm.RelationMember;
23import org.openstreetmap.josm.tools.AlphanumComparator;
24
25/**
26 * This class sorts the relation members by connectivity.
27 * <p>
28 * Multiple {@link AdditionalSorter}s are implemented to handle special relation types.
29 */
30public class RelationSorter {
31
32 private interface AdditionalSorter {
33 boolean acceptsMember(List<RelationMember> relationMembers, RelationMember m);
34
35 List<RelationMember> sortMembers(List<RelationMember> list);
36 }
37
38 private static final Collection<AdditionalSorter> ADDITIONAL_SORTERS = Arrays.asList(
39 // first adequate sorter is used, so order matters
40 new AssociatedStreetRoleStreetSorter(),
41 new AssociatedStreetRoleAddressHouseSorter(),
42 new PublicTransportRoleStopPlatformSorter(),
43 new FromViaToSorter()
44 );
45
46 /**
47 * Class that sorts the {@code street} members of
48 * {@code type=associatedStreet} and {@code type=street} relations.
49 */
50 private static class AssociatedStreetRoleStreetSorter implements AdditionalSorter {
51
52 @Override
53 public boolean acceptsMember(List<RelationMember> relationMembers, RelationMember m) {
54 return "street".equals(m.getRole());
55 }
56
57 @Override
58 public List<RelationMember> sortMembers(List<RelationMember> list) {
59 return sortMembersByConnectivity(list);
60 }
61 }
62
63 /**
64 * Class that sorts the {@code address} and {@code house} members of
65 * {@code type=associatedStreet} and {@code type=street} relations.
66 */
67 private static class AssociatedStreetRoleAddressHouseSorter implements AdditionalSorter {
68
69 @Override
70 public boolean acceptsMember(List<RelationMember> relationMembers, RelationMember m) {
71 return m.hasRole("address", "house");
72 }
73
74 @Override
75 public List<RelationMember> sortMembers(List<RelationMember> list) {
76 list.sort((a, b) -> {
77 final int houseNumber = AlphanumComparator.getInstance().compare(
78 a.getMember().get("addr:housenumber"),
79 b.getMember().get("addr:housenumber"));
80 if (houseNumber != 0) {
81 return houseNumber;
82 }
83 final String aDisplayName = a.getMember().getDisplayName(DefaultNameFormatter.getInstance());
84 final String bDisplayName = b.getMember().getDisplayName(DefaultNameFormatter.getInstance());
85 return AlphanumComparator.getInstance().compare(aDisplayName, bDisplayName);
86 });
87 return list;
88 }
89 }
90
91 /**
92 * Class that sorts the {@code platform} and {@code stop} members of
93 * {@code type=public_transport} relations.
94 */
95 private static class PublicTransportRoleStopPlatformSorter implements AdditionalSorter {
96
97 @Override
98 public boolean acceptsMember(List<RelationMember> relationMembers, RelationMember m) {
99 return m.getRole() != null && (m.getRole().startsWith("platform") || m.getRole().startsWith("stop"));
100 }
101
102 private static String getStopName(OsmPrimitive p) {
103 return p.referrers(Relation.class)
104 .filter(ref -> ref.hasTag("type", "public_transport")
105 && ref.hasTag("public_transport", "stop_area")
106 && ref.getName() != null)
107 .map(Relation::getName)
108 .findFirst()
109 .orElse(p.getName());
110 }
111
112 @Override
113 public List<RelationMember> sortMembers(List<RelationMember> list) {
114 final Map<String, RelationMember> platformByName = new HashMap<>();
115 if (list.stream()
116 .filter(i -> i.getRole().startsWith("platform"))
117 .map(i -> platformByName.put(getStopName(i.getMember()), i))
118 .anyMatch(Objects::nonNull)) {
119 // Platform with same name present. Stop to avoid damaging complicated relations.
120 // This case can happily be handled differently.
121 return list;
122 }
123 final List<RelationMember> sorted = new ArrayList<>(list.size());
124 for (RelationMember i : list) {
125 if (i.getRole().startsWith("stop")) {
126 sorted.add(i);
127 final RelationMember platform = platformByName.remove(getStopName(i.getMember()));
128 if (platform != null) {
129 sorted.add(platform);
130 }
131 }
132 }
133 sorted.addAll(platformByName.values());
134 return sorted;
135 }
136 }
137
138 /**
139 * Class that sorts the {@code from}, {@code via} and {@code to} members of
140 * {@code type=restriction} relations.
141 */
142 private static class FromViaToSorter implements AdditionalSorter {
143
144 private static final List<String> ROLES = Arrays.asList("from", "via", "to");
145
146 @Override
147 public boolean acceptsMember(List<RelationMember> relationMembers, RelationMember m) {
148 return ROLES.contains(m.getRole())
149 && relationMembers.stream().map(RelationMember::getRole).collect(Collectors.toSet()).containsAll(ROLES);
150 }
151
152 @Override
153 public List<RelationMember> sortMembers(List<RelationMember> list) {
154 list.sort(Comparator.comparingInt(m -> ROLES.indexOf(m.getRole())));
155 return list;
156 }
157 }
158
159 /**
160 * Sort a collection of relation members by the way they are linked.
161 *
162 * @param relationMembers collection of relation members
163 * @return sorted collection of relation members
164 */
165 public List<RelationMember> sortMembers(List<RelationMember> relationMembers) {
166 List<RelationMember> newMembers = new ArrayList<>();
167
168 // Sort members with custom mechanisms (relation-dependent)
169 List<RelationMember> defaultMembers = new ArrayList<>(relationMembers.size());
170 // Maps sorter to assigned members for sorting. Use LinkedHashMap to retain order.
171 Map<AdditionalSorter, List<RelationMember>> customMap = new LinkedHashMap<>();
172
173 // Dispatch members to the first adequate sorter
174 for (RelationMember m : relationMembers) {
175 boolean wasAdded = false;
176 for (AdditionalSorter sorter : ADDITIONAL_SORTERS) {
177 if (sorter.acceptsMember(relationMembers, m)) {
178 wasAdded = customMap.computeIfAbsent(sorter, k -> new LinkedList<>()).add(m);
179 break;
180 }
181 }
182 if (!wasAdded) {
183 defaultMembers.add(m);
184 }
185 }
186
187 // Sort members and add them to result
188 for (Entry<AdditionalSorter, List<RelationMember>> entry : customMap.entrySet()) {
189 newMembers.addAll(entry.getKey().sortMembers(entry.getValue()));
190 }
191 newMembers.addAll(sortMembersByConnectivity(defaultMembers));
192 return newMembers;
193 }
194
195 /**
196 * Sorts a list of members by connectivity
197 * @param defaultMembers The members to sort
198 * @return A sorted list of the same members
199 * @since 17862 (signature change, generics)
200 */
201 public static <T extends IRelationMember<? extends IPrimitive>> List<T> sortMembersByConnectivity(List<T> defaultMembers) {
202 List<T> newMembers;
203
204 RelationNodeMap<T> map = new RelationNodeMap<>(defaultMembers);
205 // List of groups of linked members
206 //
207 List<LinkedList<Integer>> allGroups = new ArrayList<>();
208
209 // current group of members that are linked among each other
210 // Two successive members are always linked i.e. have a common node.
211 //
212 LinkedList<Integer> group;
213
214 Integer first;
215 while ((first = map.pop()) != null) {
216 group = new LinkedList<>();
217 group.add(first);
218
219 allGroups.add(group);
220
221 Integer next = first;
222 while ((next = map.popAdjacent(next)) != null) {
223 group.addLast(next);
224 }
225
226 // The first element need not be in front of the list.
227 // So the search goes in both directions
228 //
229 next = first;
230 while ((next = map.popAdjacent(next)) != null) {
231 group.addFirst(next);
232 }
233 }
234
235 newMembers = allGroups.stream().flatMap(Collection::stream).map(defaultMembers::get).collect(Collectors.toList());
236
237 // Finally, add members that have not been sorted at all
238 for (Integer i : map.getNotSortableMembers()) {
239 newMembers.add(defaultMembers.get(i));
240 }
241
242 return newMembers;
243 }
244
245}
Note: See TracBrowser for help on using the repository browser.