source: josm/trunk/scripts/SyncEditorImageryIndex.groovy @ 11410

Last change on this file since 11410 was 11410, checked in by stoecker, 4 years ago

see #12706 - show mismatching shape information

  • Property svn:eol-style set to native
File size: 15.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2/**
3 * Compare and analyse the differences of the editor imagery index and the JOSM imagery list.
4 * The goal is to keep both lists in sync.
5 *
6 * The editor imagery index project (https://github.com/osmlab/editor-imagery-index)
7 * provides also a version in the JOSM format, but the JSON is the original source
8 * format, so we read that.
9 *
10 * How to run:
11 * -----------
12 *
13 * Main JOSM binary needs to be in classpath, e.g.
14 *
15 * $ groovy -cp ../dist/josm-custom.jar SyncEditorImageryIndex.groovy
16 *
17 * Add option "-h" to show the available command line flags.
18 */
19import javax.json.Json
20import javax.json.JsonArray
21import javax.json.JsonObject
22import javax.json.JsonReader
23
24import org.openstreetmap.josm.data.imagery.ImageryInfo
25import org.openstreetmap.josm.data.imagery.Shape
26import org.openstreetmap.josm.io.imagery.ImageryReader
27
28class SyncEditorImageryIndex {
29
30    List<ImageryInfo> josmEntries;
31    JsonArray eiiEntries;
32
33    def eiiUrls = new HashMap<String, JsonObject>()
34    def josmUrls = new HashMap<String, ImageryInfo>()
35
36    static String eiiInputFile = 'imagery.json'
37    static String josmInputFile = 'maps.xml'
38    static String ignoreInputFile = 'maps_ignores.txt'
39    static FileWriter outputFile = null
40    static BufferedWriter outputStream = null
41    int skipCount = 0;
42    String skipColor = "greenyellow" // should never be visible
43    def skipEntries = [:]
44    def skipColors = [:]
45
46    static def options
47
48    /**
49     * Main method.
50     */
51    static main(def args) {
52        parse_command_line_arguments(args)
53        def script = new SyncEditorImageryIndex()
54        script.loadSkip()
55        script.start()
56        script.loadJosmEntries()
57        script.loadEIIEntries()
58        script.checkInOneButNotTheOther()
59        script.checkCommonEntries()
60        script.end()
61        if(outputStream != null) {
62            outputStream.close();
63        }
64        if(outputFile != null) {
65            outputFile.close();
66        }
67    }
68
69    /**
70     * Parse command line arguments.
71     */
72    static void parse_command_line_arguments(args) {
73        def cli = new CliBuilder(width: 160)
74        cli.o(longOpt:'output', args:1, argName: "output", "Output file, - prints to stdout (default: -)")
75        cli.e(longOpt:'eii_input', args:1, argName:"eii_input", "Input file for the editor imagery index (json). Default is $eiiInputFile (current directory).")
76        cli.j(longOpt:'josm_input', args:1, argName:"josm_input", "Input file for the JOSM imagery list (xml). Default is $josmInputFile (current directory).")
77        cli.i(longOpt:'ignore_input', args:1, argName:"ignore_input", "Input file for the ignore list. Default is $ignoreInputFile (current directory).")
78        cli.s(longOpt:'shorten', "shorten the output, so it is easier to read in a console window")
79        cli.n(longOpt:'noskip', argName:"noskip", "don't skip known entries")
80        cli.x(longOpt:'xhtmlbody', argName:"xhtmlbody", "create XHTML body for display in a web page")
81        cli.X(longOpt:'xhtml', argName:"xhtml", "create XHTML for display in a web page")
82        cli.m(longOpt:'nomissingeii', argName:"nomissingeii", "don't show missing editor imagery index entries")
83        cli.h(longOpt:'help', "show this help")
84        options = cli.parse(args)
85
86        if (options.h) {
87            cli.usage()
88            System.exit(0)
89        }
90        if (options.eii_input) {
91            eiiInputFile = options.eii_input
92        }
93        if (options.josm_input) {
94            josmInputFile = options.josm_input
95        }
96        if (options.ignore_input) {
97            ignoreInputFile = options.ignore_input
98        }
99        if (options.output && options.output != "-") {
100            outputFile = new FileWriter(options.output)
101            outputStream = new BufferedWriter(outputFile)
102        }
103    }
104
105    void loadSkip() {
106        FileReader fr = new FileReader(ignoreInputFile)
107        def line
108
109        while((line = fr.readLine()) != null) {
110            def res = (line =~ /^\|\| *(\d) *\|\| *(EII|Ignore) *\|\| *\{\{\{(.+)\}\}\} *\|\|/)
111            if(res.count)
112            {
113                skipEntries[res[0][3]] = res[0][1] as int
114                if(res[0][2].equals("Ignore")) {
115                    skipColors[res[0][3]] = "green"
116                } else {
117                    skipColors[res[0][3]] = "darkgoldenrod"
118                }
119            }
120        }
121    }
122
123    void myprintlnfinal(String s) {
124        if(outputStream != null) {
125            outputStream.write(s);
126            outputStream.newLine();
127        } else {
128            println s;
129        }
130    }
131
132    void myprintln(String s) {
133        if(skipEntries.containsKey(s)) {
134            skipCount = skipEntries.get(s)
135            skipEntries.remove(s)
136            if(skipColors.containsKey(s)) {
137                skipColor = skipColors.get(s)
138            } else {
139                skipColor = "greenyellow"
140            }
141        }
142        if(skipCount) {
143            skipCount -= 1;
144            if(options.xhtmlbody || options.xhtml) {
145                s = "<pre style=\"margin:3px;color:"+skipColor+"\">"+s.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")+"</pre>"
146            }
147            if (!options.noskip) {
148                return;
149            }
150        } else if(options.xhtmlbody || options.xhtml) {
151            String color = s.startsWith("***") ? "black" : (s.startsWith("+ ") ? "blue" : "red")
152            s = "<pre style=\"margin:3px;color:"+color+"\">"+s.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")+"</pre>"
153        }
154        myprintlnfinal(s)
155    }
156
157    void start() {
158        if (options.xhtml) {
159            myprintlnfinal "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
160            myprintlnfinal "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/><title>JOSM - EII differences</title></head><body>\n"
161        }
162    }
163
164    void end() {
165        for (def s: skipEntries.keySet()) {
166            myprintln "+++ Obsolete skip entry: " + s
167        }
168        if (options.xhtml) {
169            myprintlnfinal "</body></html>\n"
170        }
171    }
172
173    void loadEIIEntries() {
174        FileReader fr = new FileReader(eiiInputFile)
175        JsonReader jr = Json.createReader(fr)
176        eiiEntries = jr.readArray()
177        jr.close()
178
179        for (def e : eiiEntries) {
180            def url = getUrl(e)
181            if (url.contains("{z}")) {
182                myprintln "+++ EII-URL uses {z} instead of {zoom}: "+url
183                url = url.replace("{z}","{zoom}")
184            }
185            if (eiiUrls.containsKey(url)) {
186                myprintln "+++ EII-URL is not unique: "+url
187            } else {
188                eiiUrls.put(url, e)
189            }
190        }
191        myprintln "*** Loaded ${eiiEntries.size()} entries (EII). ***"
192    }
193
194    void loadJosmEntries() {
195        def reader = new ImageryReader(josmInputFile)
196        josmEntries = reader.parse()
197
198        for (def e : josmEntries) {
199            def url = getUrl(e)
200            if (url.contains("{z}")) {
201                myprintln "+++ JOSM-URL uses {z} instead of {zoom}: "+url
202                url = url.replace("{z}","{zoom}")
203            }
204            if (josmUrls.containsKey(url)) {
205                myprintln "+++ JOSM-URL is not unique: "+url
206            } else {
207              josmUrls.put(url, e)
208            }
209            for (def m : e.getMirrors()) {
210                url = getUrl(m)
211                if (josmUrls.containsKey(url)) {
212                    myprintln "+++ JOSM-Mirror-URL is not unique: "+url
213                } else {
214                  josmUrls.put(url, m)
215                }
216            }
217        }
218        myprintln "*** Loaded ${josmEntries.size()} entries (JOSM). ***"
219    }
220
221    List inOneButNotTheOther(Map m1, Map m2) {
222        def l = []
223        for (def url : m1.keySet()) {
224            if (!m2.containsKey(url)) {
225                def name = getName(m1.get(url))
226                l += "  "+getDescription(m1.get(url))
227            }
228        }
229        l.sort()
230    }
231
232    void checkInOneButNotTheOther() {
233        def l1 = inOneButNotTheOther(eiiUrls, josmUrls)
234        myprintln "*** URLs found in EII but not in JOSM (${l1.size()}): ***"
235        if (!l1.isEmpty()) {
236            for (def l : l1)
237                myprintln "-"+l
238        }
239
240        if (options.nomissingeii)
241            return
242        def l2 = inOneButNotTheOther(josmUrls, eiiUrls)
243        myprintln "*** URLs found in JOSM but not in EII (${l2.size()}): ***"
244        if (!l2.isEmpty()) {
245            for (def l : l2)
246                myprintln "+" + l
247        }
248    }
249
250    void checkCommonEntries() {
251        myprintln "*** Same URL, but different name: ***"
252        for (def url : eiiUrls.keySet()) {
253            def e = eiiUrls.get(url)
254            if (!josmUrls.containsKey(url)) continue
255            def j = josmUrls.get(url)
256            if (!getName(e).equals(getName(j))) {
257                myprintln "  name differs: $url"
258                myprintln "     (EII):     ${getName(e)}"
259                myprintln "     (JOSM):    ${getName(j)}"
260            }
261        }
262
263        myprintln "*** Same URL, but different type: ***"
264        for (def url : eiiUrls.keySet()) {
265            def e = eiiUrls.get(url)
266            if (!josmUrls.containsKey(url)) continue
267            def j = josmUrls.get(url)
268            if (!getType(e).equals(getType(j))) {
269                myprintln "  type differs: ${getName(j)} - $url"
270                myprintln "     (EII):     ${getType(e)}"
271                myprintln "     (JOSM):    ${getType(j)}"
272            }
273        }
274
275        myprintln "*** Same URL, but different zoom bounds: ***"
276        for (def url : eiiUrls.keySet()) {
277            def e = eiiUrls.get(url)
278            if (!josmUrls.containsKey(url)) continue
279            def j = josmUrls.get(url)
280
281            Integer eMinZoom = getMinZoom(e)
282            Integer jMinZoom = getMinZoom(j)
283            if (eMinZoom != jMinZoom  && !(eMinZoom == 0 && jMinZoom == null)) {
284                myprintln "  minzoom differs: ${getDescription(j)}"
285                myprintln "     (EII):     ${eMinZoom}"
286                myprintln "     (JOSM):    ${jMinZoom}"
287            }
288            Integer eMaxZoom = getMaxZoom(e)
289            Integer jMaxZoom = getMaxZoom(j)
290            if (eMaxZoom != jMaxZoom) {
291                myprintln "  maxzoom differs: ${getDescription(j)}"
292                myprintln "     (EII):     ${eMaxZoom}"
293                myprintln "     (JOSM):    ${jMaxZoom}"
294            }
295        }
296
297        myprintln "*** Same URL, but different country code: ***"
298        for (def url : eiiUrls.keySet()) {
299            def e = eiiUrls.get(url)
300            if (!josmUrls.containsKey(url)) continue
301            def j = josmUrls.get(url)
302            if (!getCountryCode(e).equals(getCountryCode(j))) {
303                myprintln "  country code differs: ${getDescription(j)}"
304                myprintln "     (EII):     ${getCountryCode(e)}"
305                myprintln "     (JOSM):    ${getCountryCode(j)}"
306            }
307        }
308        /*myprintln "*** Same URL, but different quality: ***"
309        for (def url : eiiUrls.keySet()) {
310            def e = eiiUrls.get(url)
311            if (!josmUrls.containsKey(url)) {
312              def q = getQuality(e)
313              if("best".equals(q)) {
314                myprintln "  quality best entry not in JOSM for ${getDescription(e)}"
315              }
316              continue
317            }
318            def j = josmUrls.get(url)
319            if (!getQuality(e).equals(getQuality(j))) {
320                myprintln "  quality differs: ${getDescription(j)}"
321                myprintln "     (EII):     ${getQuality(e)}"
322                myprintln "     (JOSM):    ${getQuality(j)}"
323            }
324        }*/
325        myprintln "*** Mismatching shapes: ***"
326        for (def url : josmUrls.keySet()) {
327            def j = josmUrls.get(url)
328            def num = 1
329            for (def shape : getShapes(j)) {
330                def p = shape.getPoints()
331                if(!p[0].equals(p[p.size()-1])) {
332                    myprintln "+++ JOSM shape $num unclosed: ${getDescription(j)}"
333                }
334                ++num
335            }
336        }
337        for (def url : eiiUrls.keySet()) {
338            def e = eiiUrls.get(url)
339            def num = 1
340            def s = getShapes(e)
341            for (def shape : s) {
342                def p = shape.getPoints()
343                if(!p[0].equals(p[p.size()-1])) {
344                    myprintln "+++ EII shape $num unclosed: ${getDescription(e)}"
345                }
346                ++num
347            }
348            if (!josmUrls.containsKey(url)) {
349                continue
350            }
351            def j = josmUrls.get(url)
352            if(!s.equals(getShapes(j))) {
353                myprintln " Different shapes: ${getDescription(j)}"
354            }
355        }
356    }
357
358    /**
359     * Utility functions that allow uniform access for both ImageryInfo and JsonObject.
360     */
361    static String getUrl(Object e) {
362        if (e instanceof ImageryInfo) return e.url
363        return e.getString("url")
364    }
365    static String getName(Object e) {
366        if (e instanceof ImageryInfo) return e.getOriginalName()
367        return e.getString("name")
368    }
369    static List<Shape> getShapes(Object e) {
370        if (e instanceof ImageryInfo) {
371          def bounds = e.getBounds();
372          if(bounds != null) {
373            return bounds.getShapes();
374          }
375          return []
376        }
377        def ex = e.get("extent")
378        if(ex != null) {
379            def poly = ex.get("polygon")
380            if(poly != null) {
381                List<Shape> l = []
382                for(def shapes: poly) {
383                    def s = new Shape()
384                    for(def point: shapes) {
385                        def lon = point[0].toString()
386                        def lat = point[1].toString()
387                        s.addPoint(lat, lon)
388                    }
389                    l.add(s)
390                }
391                return l
392            }
393        }
394        return []
395    }
396    static String getType(Object e) {
397        if (e instanceof ImageryInfo) return e.getImageryType().getTypeString()
398        return e.getString("type")
399    }
400    static Integer getMinZoom(Object e) {
401        if (e instanceof ImageryInfo) {
402            int mz = e.getMinZoom()
403            return mz == 0 ? null : mz
404        } else {
405            def ext = e.getJsonObject("extent")
406            if (ext == null) return null
407            def num = ext.getJsonNumber("min_zoom")
408            if (num == null) return null
409            return num.intValue()
410        }
411    }
412    static Integer getMaxZoom(Object e) {
413        if (e instanceof ImageryInfo) {
414            int mz = e.getMaxZoom()
415            return mz == 0 ? null : mz
416        } else {
417            def ext = e.getJsonObject("extent")
418            if (ext == null) return null
419            def num = ext.getJsonNumber("max_zoom")
420            if (num == null) return null
421            return num.intValue()
422        }
423    }
424    static String getCountryCode(Object e) {
425        if (e instanceof ImageryInfo) return "".equals(e.getCountryCode()) ? null : e.getCountryCode()
426        return e.getString("country_code", null)
427    }
428    static String getQuality(Object e) {
429        //if (e instanceof ImageryInfo) return "".equals(e.getQuality()) ? null : e.getQuality()
430        if (e instanceof ImageryInfo) return null
431        return e.get("best") ? "best" : null
432    }
433    String getDescription(Object o) {
434        def url = getUrl(o)
435        def cc = getCountryCode(o)
436        if (cc == null) {
437            def j = josmUrls.get(url)
438            if (j != null) cc = getCountryCode(j)
439            if (cc == null) {
440                def e = eiiUrls.get(url)
441                if (e != null) cc = getCountryCode(e)
442            }
443        }
444        if (cc == null) {
445            cc = ''
446        } else {
447            cc = "[$cc] "
448        }
449        def d = cc + getName(o) + " - " + getUrl(o)
450        if (options.shorten) {
451            def MAXLEN = 140
452            if (d.length() > MAXLEN) d = d.substring(0, MAXLEN-1) + "..."
453        }
454        return d
455    }
456}
Note: See TracBrowser for help on using the repository browser.