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 }