001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.gui.mappaint.xml;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.io.InputStream;
007 import java.io.InputStreamReader;
008 import java.io.IOException;
009 import java.util.Collection;
010 import java.util.Collections;
011 import java.util.HashMap;
012 import java.util.LinkedList;
013 import java.util.List;
014
015 import org.openstreetmap.josm.Main;
016 import org.openstreetmap.josm.data.osm.Node;
017 import org.openstreetmap.josm.data.osm.OsmPrimitive;
018 import org.openstreetmap.josm.data.osm.OsmUtils;
019 import org.openstreetmap.josm.data.osm.Relation;
020 import org.openstreetmap.josm.data.osm.Way;
021 import org.openstreetmap.josm.gui.mappaint.Cascade;
022 import org.openstreetmap.josm.gui.mappaint.Keyword;
023 import org.openstreetmap.josm.gui.mappaint.MultiCascade;
024 import org.openstreetmap.josm.gui.mappaint.Range;
025 import org.openstreetmap.josm.gui.mappaint.StyleKeys;
026 import org.openstreetmap.josm.gui.mappaint.StyleSource;
027 import org.openstreetmap.josm.gui.preferences.SourceEntry;
028 import org.openstreetmap.josm.io.MirroredInputStream;
029 import org.openstreetmap.josm.tools.Utils;
030 import org.openstreetmap.josm.tools.XmlObjectParser;
031 import org.xml.sax.SAXException;
032 import org.xml.sax.SAXParseException;
033
034 public class XmlStyleSource extends StyleSource implements StyleKeys {
035
036 protected final HashMap<String, IconPrototype> icons = new HashMap<String, IconPrototype>();
037 protected final HashMap<String, LinePrototype> lines = new HashMap<String, LinePrototype>();
038 protected final HashMap<String, LinemodPrototype> modifiers = new HashMap<String, LinemodPrototype>();
039 protected final HashMap<String, AreaPrototype> areas = new HashMap<String, AreaPrototype>();
040 protected final LinkedList<IconPrototype> iconsList = new LinkedList<IconPrototype>();
041 protected final LinkedList<LinePrototype> linesList = new LinkedList<LinePrototype>();
042 protected final LinkedList<LinemodPrototype> modifiersList = new LinkedList<LinemodPrototype>();
043 protected final LinkedList<AreaPrototype> areasList = new LinkedList<AreaPrototype>();
044
045 public XmlStyleSource(String url, String name, String shortdescription) {
046 super(url, name, shortdescription);
047 }
048
049 public XmlStyleSource(SourceEntry entry) {
050 super(entry);
051 }
052
053 protected void init() {
054 super.init();
055 icons.clear();
056 lines.clear();
057 modifiers.clear();
058 areas.clear();
059 iconsList.clear();
060 linesList.clear();
061 modifiersList.clear();
062 areasList.clear();
063 }
064
065 @Override
066 public void loadStyleSource() {
067 init();
068 try {
069 InputStreamReader reader = new InputStreamReader(getSourceInputStream());
070 XmlObjectParser parser = new XmlObjectParser(new XmlStyleSourceHandler(this));
071 parser.startWithValidation(reader,
072 "http://josm.openstreetmap.de/mappaint-style-1.0",
073 "resource://data/mappaint-style.xsd");
074 while(parser.hasNext()) {
075 }
076
077 } catch(IOException e) {
078 System.err.println(tr("Warning: failed to load Mappaint styles from ''{0}''. Exception was: {1}", url, e.toString()));
079 e.printStackTrace();
080 logError(e);
081 } catch(SAXParseException e) {
082 System.err.println(tr("Warning: failed to parse Mappaint styles from ''{0}''. Error was: [{1}:{2}] {3}", url, e.getLineNumber(), e.getColumnNumber(), e.getMessage()));
083 e.printStackTrace();
084 logError(e);
085 } catch(SAXException e) {
086 System.err.println(tr("Warning: failed to parse Mappaint styles from ''{0}''. Error was: {1}", url, e.getMessage()));
087 e.printStackTrace();
088 logError(e);
089 }
090 }
091
092 public InputStream getSourceInputStream() throws IOException {
093 MirroredInputStream in = new MirroredInputStream(url);
094 InputStream zip = in.getZipEntry("xml", "style");
095 if (zip != null) {
096 zipIcons = in.getFile();
097 return zip;
098 } else {
099 zipIcons = null;
100 return in;
101 }
102 }
103
104 private static class WayPrototypesRecord {
105 public LinePrototype line;
106 public List<LinemodPrototype> linemods;
107 public AreaPrototype area;
108 }
109
110 private <T extends Prototype> T update(T current, T candidate, Double scale, MultiCascade mc) {
111 if (requiresUpdate(current, candidate, scale, mc))
112 return candidate;
113 else
114 return current;
115 }
116
117 /**
118 * checks whether a certain match is better than the current match
119 * @param current can be null
120 * @param candidate the new Prototype that could be used instead
121 * @param scale ignored if null, otherwise checks if scale is within the range of candidate
122 * @param mc side effect: update the valid region for the current MultiCascade
123 */
124 private boolean requiresUpdate(Prototype current, Prototype candidate, Double scale, MultiCascade mc) {
125 if (current == null || candidate.priority >= current.priority) {
126 if (scale == null)
127 return true;
128
129 if (candidate.range.contains(scale)) {
130 mc.range = Range.cut(mc.range, candidate.range);
131 return true;
132 } else {
133 mc.range = mc.range.reduceAround(scale, candidate.range);
134 return false;
135 }
136 }
137 return false;
138 }
139
140 private IconPrototype getNode(OsmPrimitive primitive, Double scale, MultiCascade mc) {
141 IconPrototype icon = null;
142 for (String key : primitive.keySet()) {
143 String val = primitive.get(key);
144 IconPrototype p;
145 if ((p = icons.get("n" + key + "=" + val)) != null) {
146 icon = update(icon, p, scale, mc);
147 }
148 if ((p = icons.get("b" + key + "=" + OsmUtils.getNamedOsmBoolean(val))) != null) {
149 icon = update(icon, p, scale, mc);
150 }
151 if ((p = icons.get("x" + key)) != null) {
152 icon = update(icon, p, scale, mc);
153 }
154 }
155 for (IconPrototype s : iconsList) {
156 if (s.check(primitive))
157 {
158 icon = update(icon, s, scale, mc);
159 }
160 }
161 return icon;
162 }
163
164 /**
165 * @param closed The primitive is a closed way or we pretend it is closed.
166 * This is useful for multipolygon relations and outer ways of untagged
167 * multipolygon relations.
168 */
169 private void get(OsmPrimitive primitive, boolean closed, WayPrototypesRecord p, Double scale, MultiCascade mc) {
170 String lineIdx = null;
171 HashMap<String, LinemodPrototype> overlayMap = new HashMap<String, LinemodPrototype>();
172 boolean isNotArea = OsmUtils.isFalse(primitive.get("area"));
173 for (String key : primitive.keySet()) {
174 String val = primitive.get(key);
175 AreaPrototype styleArea;
176 LinePrototype styleLine;
177 LinemodPrototype styleLinemod;
178 String idx = "n" + key + "=" + val;
179 if ((styleArea = areas.get(idx)) != null && (closed || !styleArea.closed) && !isNotArea) {
180 p.area = update(p.area, styleArea, scale, mc);
181 }
182 if ((styleLine = lines.get(idx)) != null) {
183 if (requiresUpdate(p.line, styleLine, scale, mc)) {
184 p.line = styleLine;
185 lineIdx = idx;
186 }
187 }
188 if ((styleLinemod = modifiers.get(idx)) != null) {
189 if (requiresUpdate(null, styleLinemod, scale, mc)) {
190 overlayMap.put(idx, styleLinemod);
191 }
192 }
193 idx = "b" + key + "=" + OsmUtils.getNamedOsmBoolean(val);
194 if ((styleArea = areas.get(idx)) != null && (closed || !styleArea.closed) && !isNotArea) {
195 p.area = update(p.area, styleArea, scale, mc);
196 }
197 if ((styleLine = lines.get(idx)) != null) {
198 if (requiresUpdate(p.line, styleLine, scale, mc)) {
199 p.line = styleLine;
200 lineIdx = idx;
201 }
202 }
203 if ((styleLinemod = modifiers.get(idx)) != null) {
204 if (requiresUpdate(null, styleLinemod, scale, mc)) {
205 overlayMap.put(idx, styleLinemod);
206 }
207 }
208 idx = "x" + key;
209 if ((styleArea = areas.get(idx)) != null && (closed || !styleArea.closed) && !isNotArea) {
210 p.area = update(p.area, styleArea, scale, mc);
211 }
212 if ((styleLine = lines.get(idx)) != null) {
213 if (requiresUpdate(p.line, styleLine, scale, mc)) {
214 p.line = styleLine;
215 lineIdx = idx;
216 }
217 }
218 if ((styleLinemod = modifiers.get(idx)) != null) {
219 if (requiresUpdate(null, styleLinemod, scale, mc)) {
220 overlayMap.put(idx, styleLinemod);
221 }
222 }
223 }
224 for (AreaPrototype s : areasList) {
225 if ((closed || !s.closed) && !isNotArea && s.check(primitive)) {
226 p.area = update(p.area, s, scale, mc);
227 }
228 }
229 for (LinePrototype s : linesList) {
230 if (s.check(primitive)) {
231 p.line = update(p.line, s, scale, mc);
232 }
233 }
234 for (LinemodPrototype s : modifiersList) {
235 if (s.check(primitive)) {
236 if (requiresUpdate(null, s, scale, mc)) {
237 overlayMap.put(s.getCode(), s);
238 }
239 }
240 }
241 overlayMap.remove(lineIdx); // do not use overlay if linestyle is from the same rule (example: railway=tram)
242 if (!overlayMap.isEmpty()) {
243 List<LinemodPrototype> tmp = new LinkedList<LinemodPrototype>();
244 if (p.linemods != null) {
245 tmp.addAll(p.linemods);
246 }
247 tmp.addAll(overlayMap.values());
248 Collections.sort(tmp);
249 p.linemods = tmp;
250 }
251 }
252
253 public void add(XmlCondition c, Collection<XmlCondition> conditions, Prototype prot) {
254 if(conditions != null)
255 {
256 prot.conditions = conditions;
257 if (prot instanceof IconPrototype) {
258 iconsList.add((IconPrototype) prot);
259 } else if (prot instanceof LinemodPrototype) {
260 modifiersList.add((LinemodPrototype) prot);
261 } else if (prot instanceof LinePrototype) {
262 linesList.add((LinePrototype) prot);
263 } else if (prot instanceof AreaPrototype) {
264 areasList.add((AreaPrototype) prot);
265 } else
266 throw new RuntimeException();
267 }
268 else {
269 String key = c.getKey();
270 prot.code = key;
271 if (prot instanceof IconPrototype) {
272 icons.put(key, (IconPrototype) prot);
273 } else if (prot instanceof LinemodPrototype) {
274 modifiers.put(key, (LinemodPrototype) prot);
275 } else if (prot instanceof LinePrototype) {
276 lines.put(key, (LinePrototype) prot);
277 } else if (prot instanceof AreaPrototype) {
278 areas.put(key, (AreaPrototype) prot);
279 } else
280 throw new RuntimeException();
281 }
282 }
283
284 @Override
285 public void apply(MultiCascade mc, OsmPrimitive osm, double scale, OsmPrimitive multipolyOuterWay, boolean pretendWayIsClosed) {
286 Cascade def = mc.getOrCreateCascade("default");
287 boolean useMinMaxScale = Main.pref.getBoolean("mappaint.zoomLevelDisplay", false);
288
289 if (osm instanceof Node || (osm instanceof Relation && "restriction".equals(osm.get("type")))) {
290 IconPrototype icon = getNode(osm, (useMinMaxScale ? scale : null), mc);
291 if (icon != null) {
292 def.put(ICON_IMAGE, icon.icon);
293 if (osm instanceof Node) {
294 if (icon.annotate != null) {
295 if (icon.annotate) {
296 def.put(TEXT, Keyword.AUTO);
297 } else {
298 def.remove(TEXT);
299 }
300 }
301 }
302 }
303 } else if (osm instanceof Way || (osm instanceof Relation && ((Relation)osm).isMultipolygon())) {
304 WayPrototypesRecord p = new WayPrototypesRecord();
305 get(osm, pretendWayIsClosed || !(osm instanceof Way) || ((Way) osm).isClosed(), p, (useMinMaxScale ? scale : null), mc);
306 if (p.line != null) {
307 def.put(WIDTH, new Float(p.line.getWidth()));
308 def.putOrClear(REAL_WIDTH, p.line.realWidth != null ? new Float(p.line.realWidth) : null);
309 def.putOrClear(COLOR, p.line.color);
310 if (p.line.color != null) {
311 int alpha = p.line.color.getAlpha();
312 if (alpha != 255) {
313 def.put(OPACITY, Utils.color_int2float(alpha));
314 }
315 }
316 def.putOrClear(DASHES, p.line.getDashed());
317 def.putOrClear(DASHES_BACKGROUND_COLOR, p.line.dashedColor);
318 }
319 Float refWidth = def.get(WIDTH, null, Float.class);
320 if (refWidth != null && p.linemods != null) {
321 int numOver = 0, numUnder = 0;
322
323 while (mc.hasLayer(String.format("over_%d", ++numOver))) {}
324 while (mc.hasLayer(String.format("under_%d", ++numUnder))) {}
325
326 for (LinemodPrototype mod : p.linemods) {
327 Cascade c;
328 if (mod.over) {
329 String layer = String.format("over_%d", numOver);
330 c = mc.getOrCreateCascade(layer);
331 c.put(OBJECT_Z_INDEX, new Float(numOver));
332 ++numOver;
333 } else {
334 String layer = String.format("under_%d", numUnder);
335 c = mc.getOrCreateCascade(layer);
336 c.put(OBJECT_Z_INDEX, new Float(-numUnder));
337 ++numUnder;
338 }
339 c.put(WIDTH, new Float(mod.getWidth(refWidth)));
340 c.putOrClear(COLOR, mod.color);
341 if (mod.color != null) {
342 int alpha = mod.color.getAlpha();
343 if (alpha != 255) {
344 c.put(OPACITY, Utils.color_int2float(alpha));
345 }
346 }
347 c.putOrClear(DASHES, mod.getDashed());
348 c.putOrClear(DASHES_BACKGROUND_COLOR, mod.dashedColor);
349 }
350 }
351 if (multipolyOuterWay != null) {
352 WayPrototypesRecord p2 = new WayPrototypesRecord();
353 get(multipolyOuterWay, true, p2, (useMinMaxScale ? scale : null), mc);
354 if (Utils.equal(p.area, p2.area)) {
355 p.area = null;
356 }
357 }
358 if (p.area != null) {
359 def.putOrClear(FILL_COLOR, p.area.color);
360 def.putOrClear(TEXT_POSITION, Keyword.CENTER);
361 def.putOrClear(TEXT, Keyword.AUTO);
362 def.remove(FILL_IMAGE);
363 }
364 }
365 }
366
367 }