source: josm/trunk/scripts/SyncEditorLayerIndex.groovy @ 13526

Last change on this file since 13526 was 13526, checked in by stoecker, 15 months ago

add some more WMS check, different color for now to no get too red

  • Property svn:eol-style set to native
File size: 39.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2/**
3 * Compare and analyse the differences of the editor layer index and the JOSM imagery list.
4 * The goal is to keep both lists in sync.
5 *
6 * The editor layer index project (https://github.com/osmlab/editor-layer-index)
7 * provides also a version in the JOSM format, but the GEOJSON 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 SyncEditorLayerIndex.groovy
16 *
17 * Add option "-h" to show the available command line flags.
18 */
19import java.text.DecimalFormat
20import javax.json.Json
21import javax.json.JsonArray
22import javax.json.JsonObject
23import javax.json.JsonReader
24
25import org.openstreetmap.josm.data.imagery.ImageryInfo
26import org.openstreetmap.josm.data.imagery.Shape
27import org.openstreetmap.josm.data.projection.Projections;
28import org.openstreetmap.josm.io.imagery.ImageryReader
29
30class SyncEditorLayerIndex {
31
32    List<ImageryInfo> josmEntries;
33    JsonArray eliEntries;
34
35    def eliUrls = new HashMap<String, JsonObject>()
36    def josmUrls = new HashMap<String, ImageryInfo>()
37    def josmMirrors = new HashMap<String, ImageryInfo>()
38
39    static String eliInputFile = 'imagery_eli.geojson'
40    static String josmInputFile = 'imagery_josm.imagery.xml'
41    static String ignoreInputFile = 'imagery_josm.ignores.txt'
42    static FileOutputStream outputFile = null
43    static OutputStreamWriter outputStream = null
44    def skip = [:]
45
46    static def options
47
48    /**
49     * Main method.
50     */
51    static main(def args) {
52        Locale.setDefault(Locale.ROOT);
53        parse_command_line_arguments(args)
54        def script = new SyncEditorLayerIndex()
55        script.loadSkip()
56        script.start()
57        script.loadJosmEntries()
58        if(options.josmxml) {
59            def file = new FileOutputStream(options.josmxml)
60            def stream = new OutputStreamWriter(file, "UTF-8")
61            script.printentries(script.josmEntries, stream)
62            stream.close();
63            file.close();
64        }
65        script.loadELIEntries()
66        if(options.elixml) {
67            def file = new FileOutputStream(options.elixml)
68            def stream = new OutputStreamWriter(file, "UTF-8")
69            script.printentries(script.eliEntries, stream)
70            stream.close();
71            file.close();
72        }
73        script.checkInOneButNotTheOther()
74        script.checkCommonEntries()
75        script.end()
76        if(outputStream != null) {
77            outputStream.close();
78        }
79        if(outputFile != null) {
80            outputFile.close();
81        }
82    }
83
84    /**
85     * Parse command line arguments.
86     */
87    static void parse_command_line_arguments(args) {
88        def cli = new CliBuilder(width: 160)
89        cli.o(longOpt:'output', args:1, argName: "output", "Output file, - prints to stdout (default: -)")
90        cli.e(longOpt:'eli_input', args:1, argName:"eli_input", "Input file for the editor layer index (geojson). Default is $eliInputFile (current directory).")
91        cli.j(longOpt:'josm_input', args:1, argName:"josm_input", "Input file for the JOSM imagery list (xml). Default is $josmInputFile (current directory).")
92        cli.i(longOpt:'ignore_input', args:1, argName:"ignore_input", "Input file for the ignore list. Default is $ignoreInputFile (current directory).")
93        cli.s(longOpt:'shorten', "shorten the output, so it is easier to read in a console window")
94        cli.n(longOpt:'noskip', argName:"noskip", "don't skip known entries")
95        cli.x(longOpt:'xhtmlbody', argName:"xhtmlbody", "create XHTML body for display in a web page")
96        cli.X(longOpt:'xhtml', argName:"xhtml", "create XHTML for display in a web page")
97        cli.p(longOpt:'elixml', args:1, argName:"elixml", "ELI entries for use in JOSM as XML file (incomplete)")
98        cli.q(longOpt:'josmxml', args:1, argName:"josmxml", "JOSM entries reoutput as XML file (incomplete)")
99        cli.m(longOpt:'noeli', argName:"noeli", "don't show output for ELI problems")
100        cli.h(longOpt:'help', "show this help")
101        options = cli.parse(args)
102
103        if (options.h) {
104            cli.usage()
105            System.exit(0)
106        }
107        if (options.eli_input) {
108            eliInputFile = options.eli_input
109        }
110        if (options.josm_input) {
111            josmInputFile = options.josm_input
112        }
113        if (options.ignore_input) {
114            ignoreInputFile = options.ignore_input
115        }
116        if (options.output && options.output != "-") {
117            outputFile = new FileOutputStream(options.output)
118            outputStream = new OutputStreamWriter(outputFile, "UTF-8")
119        }
120    }
121
122    void loadSkip() {
123        def fr = new InputStreamReader(new FileInputStream(ignoreInputFile), "UTF-8")
124        def line
125
126        while((line = fr.readLine()) != null) {
127            def res = (line =~ /^\|\| *(ELI|Ignore) *\|\| *\{\{\{(.+)\}\}\} *\|\|/)
128            if(res.count)
129            {
130                if(res[0][1].equals("Ignore")) {
131                    skip[res[0][2]] = "green"
132                } else {
133                    skip[res[0][2]] = "darkgoldenrod"
134                }
135            }
136        }
137    }
138
139    void myprintlnfinal(String s) {
140        if(outputStream != null) {
141            outputStream.write(s+System.getProperty("line.separator"))
142        } else {
143            println s
144        }
145    }
146
147    void myprintln(String s) {
148        if(skip.containsKey(s)) {
149            String color = skip.get(s)
150            skip.remove(s)
151            if(options.xhtmlbody || options.xhtml) {
152                s = "<pre style=\"margin:3px;color:"+color+"\">"+s.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")+"</pre>"
153            }
154            if (!options.noskip) {
155                return
156            }
157        } else if(options.xhtmlbody || options.xhtml) {
158            String color = s.startsWith("***") ? "black" : ((s.startsWith("+ ") || s.startsWith("+++ ELI")) ? "blue" :
159            (s.startsWith("#") ? "indigo" : (s.startsWith("!") ? "orange" : "red")))
160            s = "<pre style=\"margin:3px;color:"+color+"\">"+s.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")+"</pre>"
161        }
162        if ((s.startsWith("+ ") || s.startsWith("+++ ELI") || s.startsWith("#")) && options.noeli) {
163            return
164        }
165        myprintlnfinal(s)
166    }
167
168    void start() {
169        if (options.xhtml) {
170            myprintlnfinal "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
171            myprintlnfinal "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/><title>JOSM - ELI differences</title></head><body>\n"
172        }
173    }
174
175    void end() {
176        for (def s: skip.keySet()) {
177            myprintln "+++ Obsolete skip entry: " + s
178        }
179        if (options.xhtml) {
180            myprintlnfinal "</body></html>\n"
181        }
182    }
183
184    void loadELIEntries() {
185        FileReader fr = new FileReader(eliInputFile)
186        JsonReader jr = Json.createReader(fr)
187        eliEntries = jr.readObject().get("features")
188        jr.close()
189
190        for (def e : eliEntries) {
191            def url = getUrlStripped(e)
192            if (url.contains("{z}")) {
193                myprintln "+++ ELI-URL uses {z} instead of {zoom}: "+url
194                url = url.replace("{z}","{zoom}")
195            }
196            if (eliUrls.containsKey(url)) {
197                myprintln "+++ ELI-URL is not unique: "+url
198            } else {
199                eliUrls.put(url, e)
200            }
201        }
202        myprintln "*** Loaded ${eliEntries.size()} entries (ELI). ***"
203    }
204    String cdata(def s, boolean escape = false) {
205        if(escape) {
206            return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;")
207        } else if(s =~ /[<>&]/)
208            return "<![CDATA[$s]]>"
209       return s
210    }
211
212    String maininfo(def entry, String offset) {
213        String t = getType(entry)
214        String res = offset + "<type>$t</type>\n"
215        res += offset + "<url>${cdata(getUrl(entry))}</url>\n"
216        if(getMinZoom(entry) != null)
217            res += offset + "<min-zoom>${getMinZoom(entry)}</min-zoom>\n"
218        if(getMaxZoom(entry) != null)
219            res += offset + "<max-zoom>${getMaxZoom(entry)}</max-zoom>\n"
220        if (t == "wms") {
221            def p = getProjections(entry)
222            if (p) {
223                res += offset + "<projections>\n"
224                for (def c : p)
225                    res += offset + "    <code>$c</code>\n"
226                res += offset + "</projections>\n"
227            }
228        }
229        return res
230    }
231
232    void printentries(def entries, def stream) {
233        DecimalFormat df = new DecimalFormat("#.#######")
234        df.setRoundingMode(java.math.RoundingMode.CEILING)
235        stream.write "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
236        stream.write "<imagery xmlns=\"http://josm.openstreetmap.de/maps-1.0\">\n"
237        for (def e : entries) {
238            def best = "eli-best".equals(getQuality(e))
239            stream.write "    <entry"+(best ? " eli-best=\"true\"" : "" )+">\n"
240            stream.write "        <name>${cdata(getName(e), true)}</name>\n"
241            stream.write "        <id>${getId(e)}</id>\n"
242            def t
243            if((t = getDate(e)))
244                stream.write "        <date>$t</date>\n"
245            if((t = getCountryCode(e)))
246                stream.write "        <country-code>$t</country-code>\n"
247            if((getDefault(e)))
248                stream.write "        <default>true</default>\n"
249            stream.write maininfo(e, "        ")
250            if((t = getAttributionText(e)))
251                stream.write "        <attribution-text mandatory=\"true\">${cdata(t, true)}</attribution-text>\n"
252            if((t = getAttributionUrl(e)))
253                stream.write "        <attribution-url>${cdata(t)}</attribution-url>\n"
254            if((t = getLogoImage(e)))
255                stream.write "        <logo-image>${cdata(t, true)}</logo-image>\n"
256            if((t = getLogoUrl(e)))
257                stream.write "        <logo-url>${cdata(t)}</logo-url>\n"
258            if((t = getTermsOfUseText(e)))
259                stream.write "        <terms-of-use-text>${cdata(t, true)}</terms-of-use-text>\n"
260            if((t = getTermsOfUseUrl(e)))
261                stream.write "        <terms-of-use-url>${cdata(t)}</terms-of-use-url>\n"
262            if((t = getPermissionReferenceUrl(e)))
263                stream.write "        <permission-ref>${cdata(t)}</permission-ref>\n"
264            if((getValidGeoreference(e)))
265                stream.write "        <valid-georeference>true</valid-georeference>\n"
266            if((t = getIcon(e)))
267                stream.write "        <icon>${cdata(t)}</icon>\n"
268            for (def d : getDescriptions(e)) {
269                    stream.write "        <description lang=\"${d.getKey()}\">${d.getValue()}</description>\n"
270            }
271            for (def m : getMirrors(e)) {
272                    stream.write "        <mirror>\n"+maininfo(m, "            ")+"        </mirror>\n"
273            }
274            def minlat = 1000
275            def minlon = 1000
276            def maxlat = -1000
277            def maxlon = -1000
278            def shapes = ""
279            def sep = "\n            "
280            for(def s: getShapes(e)) {
281                shapes += "            <shape>"
282                def i = 0
283                for(def p: s.getPoints()) {
284                    def lat = p.getLat()
285                    def lon = p.getLon()
286                    if(lat > maxlat) maxlat = lat
287                    if(lon > maxlon) maxlon = lon
288                    if(lat < minlat) minlat = lat
289                    if(lon < minlon) minlon = lon
290                    if(!(i++%3)) {
291                        shapes += sep + "    "
292                    }
293                    shapes += "<point lat='${df.format(lat)}' lon='${df.format(lon)}'/>"
294                }
295                shapes += sep + "</shape>\n"
296            }
297            if(shapes) {
298                stream.write "        <bounds min-lat='${df.format(minlat)}' min-lon='${df.format(minlon)}' max-lat='${df.format(maxlat)}' max-lon='${df.format(maxlon)}'>\n"
299                stream.write shapes + "        </bounds>\n"
300            }
301            stream.write "    </entry>\n"
302        }
303        stream.write "</imagery>\n"
304        stream.close()
305    }
306
307    void loadJosmEntries() {
308        def reader = new ImageryReader(josmInputFile)
309        josmEntries = reader.parse()
310
311        for (def e : josmEntries) {
312            def url = getUrlStripped(e)
313            if (url.contains("{z}")) {
314                myprintln "+++ JOSM-URL uses {z} instead of {zoom}: "+url
315                url = url.replace("{z}","{zoom}")
316            }
317            if (josmUrls.containsKey(url)) {
318                myprintln "+++ JOSM-URL is not unique: "+url
319            } else {
320                josmUrls.put(url, e)
321            }
322            for (def m : e.getMirrors()) {
323                url = getUrlStripped(m)
324                m.origName = m.getOriginalName().replaceAll(" mirror server( \\d+)?","")
325                if (josmUrls.containsKey(url)) {
326                    myprintln "+++ JOSM-Mirror-URL is not unique: "+url
327                } else {
328                    josmUrls.put(url, m)
329                    josmMirrors.put(url, m)
330                }
331            }
332        }
333        myprintln "*** Loaded ${josmEntries.size()} entries (JOSM). ***"
334    }
335
336    List inOneButNotTheOther(Map m1, Map m2, String code, String https) {
337        def l = []
338        def k = new LinkedList<String>(m1.keySet())
339        for (def url : k) {
340            if (!m2.containsKey(url)) {
341                String urlhttps = url.replace("http:","https:")
342                if(!https || !m2.containsKey(urlhttps))
343                {
344                    def name = getName(m1.get(url))
345                    l += code+"  "+getDescription(m1.get(url))
346                }
347                else
348                {
349                    l += https+" Missing https: "+getDescription(m1.get(url))
350                    m1.put(urlhttps, m1.get(url))
351                    m1.remove(url)
352                }
353            }
354        }
355        l.sort()
356    }
357
358    void checkInOneButNotTheOther() {
359        def l1 = inOneButNotTheOther(eliUrls, josmUrls, "-", "+")
360        myprintln "*** URLs found in ELI but not in JOSM (${l1.size()}): ***"
361        if (!l1.isEmpty()) {
362            for (def l : l1) {
363                myprintln l
364            }
365        }
366
367        def l2 = inOneButNotTheOther(josmUrls, eliUrls, "+", "")
368        myprintln "*** URLs found in JOSM but not in ELI (${l2.size()}): ***"
369        if (!l2.isEmpty()) {
370            for (def l : l2) {
371                myprintln l
372            }
373        }
374    }
375
376    void checkCommonEntries() {
377        myprintln "*** Same URL, but different name: ***"
378        for (def url : eliUrls.keySet()) {
379            def e = eliUrls.get(url)
380            if (!josmUrls.containsKey(url)) continue
381            def j = josmUrls.get(url)
382            def ename = getName(e).replace("'","\u2019")
383            def jname = getName(j).replace("'","\u2019")
384            if (!ename.equals(jname)) {
385                myprintln "* Name differs ('${getName(e)}' != '${getName(j)}'): ${getUrl(j)}"
386            }
387        }
388
389        myprintln "*** Same URL, but different Id: ***"
390        for (def url : eliUrls.keySet()) {
391            def e = eliUrls.get(url)
392            if (!josmUrls.containsKey(url)) continue
393            def j = josmUrls.get(url)
394            def ename = getId(e)
395            def jname = getId(j)
396            if (!ename.equals(jname)) {
397                myprintln "# Id differs ('${getId(e)}' != '${getId(j)}'): ${getUrl(j)}"
398            }
399        }
400
401        myprintln "*** Same URL, but different type: ***"
402        for (def url : eliUrls.keySet()) {
403            def e = eliUrls.get(url)
404            if (!josmUrls.containsKey(url)) continue
405            def j = josmUrls.get(url)
406            if (!getType(e).equals(getType(j))) {
407                myprintln "* Type differs (${getType(e)} != ${getType(j)}): ${getName(j)} - ${getUrl(j)}"
408            }
409        }
410
411        myprintln "*** Same URL, but different zoom bounds: ***"
412        for (def url : eliUrls.keySet()) {
413            def e = eliUrls.get(url)
414            if (!josmUrls.containsKey(url)) continue
415            def j = josmUrls.get(url)
416
417            Integer eMinZoom = getMinZoom(e)
418            Integer jMinZoom = getMinZoom(j)
419            if (eMinZoom != jMinZoom  && !(eMinZoom == 0 && jMinZoom == null)) {
420                myprintln "* Minzoom differs (${eMinZoom} != ${jMinZoom}): ${getDescription(j)}"
421            }
422            Integer eMaxZoom = getMaxZoom(e)
423            Integer jMaxZoom = getMaxZoom(j)
424            if (eMaxZoom != jMaxZoom) {
425                myprintln "* Maxzoom differs (${eMaxZoom} != ${jMaxZoom}): ${getDescription(j)}"
426            }
427        }
428
429        myprintln "*** Same URL, but different country code: ***"
430        for (def url : eliUrls.keySet()) {
431            def e = eliUrls.get(url)
432            if (!josmUrls.containsKey(url)) continue
433            def j = josmUrls.get(url)
434            if (!getCountryCode(e).equals(getCountryCode(j))) {
435                myprintln "* Country code differs (${getCountryCode(e)} != ${getCountryCode(j)}): ${getDescription(j)}"
436            }
437        }
438        myprintln "*** Same URL, but different quality: ***"
439        for (def url : eliUrls.keySet()) {
440            def e = eliUrls.get(url)
441            if (!josmUrls.containsKey(url)) {
442              def q = getQuality(e)
443              if("eli-best".equals(q)) {
444                myprintln "- Quality best entry not in JOSM for ${getDescription(e)}"
445              }
446              continue
447            }
448            def j = josmUrls.get(url)
449            if (!getQuality(e).equals(getQuality(j))) {
450                myprintln "* Quality differs (${getQuality(e)} != ${getQuality(j)}): ${getDescription(j)}"
451            }
452        }
453        myprintln "*** Same URL, but different dates: ***"
454        for (def url : eliUrls.keySet()) {
455            def ed = getDate(eliUrls.get(url))
456            if (!josmUrls.containsKey(url)) continue
457            def j = josmUrls.get(url)
458            def jd = getDate(j)
459            // The forms 2015;- or -;2015 or 2015;2015 are handled equal to 2015
460            String ef = ed.replaceAll("\\A-;","").replaceAll(";-\\z","").replaceAll("\\A([0-9-]+);\\1\\z","\$1")
461            // ELI has a strange and inconsistent used end_date definition, so we try again with subtraction by one
462            String ed2 = ed
463            def reg = (ed =~ /^(.*;)(\d\d\d\d)(-(\d\d)(-(\d\d))?)?$/)
464            if(reg != null && reg.count == 1) {
465                Calendar cal = Calendar.getInstance()
466                cal.set(reg[0][2] as Integer, reg[0][4] == null ? 0 : (reg[0][4] as Integer)-1, reg[0][6] == null ? 1 : reg[0][6] as Integer)
467                cal.add(Calendar.DAY_OF_MONTH, -1)
468                ed2 = reg[0][1] + cal.get(Calendar.YEAR)
469                if (reg[0][4] != null)
470                    ed2 += "-" + String.format("%02d", cal.get(Calendar.MONTH)+1)
471                if (reg[0][6] != null)
472                    ed2 += "-" + String.format("%02d", cal.get(Calendar.DAY_OF_MONTH))
473            }
474            String ef2 = ed2.replaceAll("\\A-;","").replaceAll(";-\\z","").replaceAll("\\A([0-9-]+);\\1\\z","\$1")
475            if (!ed.equals(jd) && !ef.equals(jd) && !ed2.equals(jd) && !ef2.equals(jd)) {
476                String t = "'${ed}'"
477                if (!ed.equals(ef)) {
478                    t += " or '${ef}'"
479                }
480                if (jd.isEmpty()) {
481                    myprintln "- Missing JOSM date (${t}): ${getDescription(j)}"
482                } else if (!ed.isEmpty()) {
483                    myprintln "* Date differs ('${t}' != '${jd}'): ${getDescription(j)}"
484                } else if (!options.nomissingeli) {
485                    myprintln "+ Missing ELI date ('${jd}'): ${getDescription(j)}"
486                }
487            }
488        }
489        myprintln "*** Same URL, but different information: ***"
490        for (def url : eliUrls.keySet()) {
491            if (!josmUrls.containsKey(url)) continue
492            def e = eliUrls.get(url)
493            def j = josmUrls.get(url)
494
495            def et = getDescriptions(e)
496            def jt = getDescriptions(j)
497            et = (et.size() > 0) ? et["en"] : ""
498            jt = (jt.size() > 0) ? jt["en"] : ""
499            if (!et.equals(jt)) {
500                if (!jt) {
501                    myprintln "- Missing JOSM description (${et}): ${getDescription(j)}"
502                } else if (et) {
503                    myprintln "* Description differs ('${et}' != '${jt}'): ${getDescription(j)}"
504                } else if (!options.nomissingeli) {
505                    myprintln "+ Missing ELI description ('${jt}'): ${getDescription(j)}"
506                }
507            }
508
509            et = getPermissionReferenceUrl(e)
510            jt = getPermissionReferenceUrl(j)
511            if (!jt) jt = getTermsOfUseUrl(j)
512            if (!et.equals(jt)) {
513                if (!jt) {
514                    myprintln "- Missing JOSM license URL (${et}): ${getDescription(j)}"
515                } else if (et) {
516                    myprintln "* License URL differs ('${et}' != '${jt}'): ${getDescription(j)}"
517                } else if (!options.nomissingeli) {
518                    myprintln "+ Missing ELI license URL ('${jt}'): ${getDescription(j)}"
519                }
520            }
521
522            et = getAttributionUrl(e)
523            jt = getAttributionUrl(j)
524            if (!et.equals(jt)) {
525                if (!jt) {
526                    myprintln "- Missing JOSM attribution URL (${et}): ${getDescription(j)}"
527                } else if (et) {
528                    def ethttps = et.replace("http:","https:")
529                    if(jt.equals(ethttps)) {
530                        myprintln "+ Attribution URL differs ('${et}' != '${jt}'): ${getDescription(j)}"
531                    } else {
532                        myprintln "* Attribution URL differs ('${et}' != '${jt}'): ${getDescription(j)}"
533                    }
534                } else if (!options.nomissingeli) {
535                    myprintln "+ Missing ELI attribution URL ('${jt}'): ${getDescription(j)}"
536                }
537            }
538
539            et = getAttributionText(e)
540            jt = getAttributionText(j)
541            if (!et.equals(jt)) {
542                if (!jt) {
543                    myprintln "- Missing JOSM attribution text (${et}): ${getDescription(j)}"
544                } else if (et) {
545                    myprintln "* Attribution text differs ('${et}' != '${jt}'): ${getDescription(j)}"
546                } else if (!options.nomissingeli) {
547                    myprintln "+ Missing ELI attribution text ('${jt}'): ${getDescription(j)}"
548                }
549            }
550
551            et = getProjections(e)
552            jt = getProjections(j)
553            if (et) { et = new LinkedList(et); Collections.sort(et); et = String.join(" ", et) }
554            if (jt) { jt = new LinkedList(jt); Collections.sort(jt); jt = String.join(" ", jt) }
555            if (!et.equals(jt)) {
556                if (!jt) {
557                    myprintln "- Missing JOSM projections (${et}): ${getDescription(j)}"
558                } else if (et) {
559                    myprintln "* Projections differ ('${et}' != '${jt}'): ${getDescription(j)}"
560                } else if (!options.nomissingeli && !getType(e).equals("tms")) {
561                    myprintln "+ Missing ELI projections ('${jt}'): ${getDescription(j)}"
562                }
563            }
564
565            et = getDefault(e)
566            jt = getDefault(j)
567            if (!et.equals(jt)) {
568                if (!jt) {
569                    myprintln "- Missing JOSM default: ${getDescription(j)}"
570                } else if (!options.nomissingeli) {
571                    myprintln "+ Missing ELI default: ${getDescription(j)}"
572                }
573            }
574        }
575        myprintln "*** Mismatching shapes: ***"
576        for (def url : josmUrls.keySet()) {
577            def j = josmUrls.get(url)
578            def num = 1
579            for (def shape : getShapes(j)) {
580                def p = shape.getPoints()
581                if(!p[0].equals(p[p.size()-1])) {
582                    myprintln "+++ JOSM shape $num unclosed: ${getDescription(j)}"
583                }
584                for (def nump = 1; nump < p.size(); ++nump) {
585                    if (p[nump-1] == p[nump]) {
586                        myprintln "+++ JOSM shape $num double point at ${nump-1}: ${getDescription(j)}"
587                    }
588                }
589                ++num
590            }
591        }
592        for (def url : eliUrls.keySet()) {
593            def e = eliUrls.get(url)
594            def num = 1
595            def s = getShapes(e)
596            for (def shape : s) {
597                def p = shape.getPoints()
598                if(!p[0].equals(p[p.size()-1]) && !options.nomissingeli) {
599                    myprintln "+++ ELI shape $num unclosed: ${getDescription(e)}"
600                }
601                for (def nump = 1; nump < p.size(); ++nump) {
602                    if (p[nump-1] == p[nump]) {
603                        myprintln "+++ ELI shape $num double point at ${nump-1}: ${getDescription(e)}"
604                    }
605                }
606                ++num
607            }
608            if (!josmUrls.containsKey(url)) {
609                continue
610            }
611            def j = josmUrls.get(url)
612            def js = getShapes(j)
613            if(!s.size() && js.size()) {
614                if(!options.nomissingeli) {
615                    myprintln "+ No ELI shape: ${getDescription(j)}"
616                }
617            } else if(!js.size() && s.size()) {
618                // don't report boundary like 5 point shapes as difference
619                if (s.size() != 1 || s[0].getPoints().size() != 5) {
620                    myprintln "- No JOSM shape: ${getDescription(j)}"
621                }
622            } else if(s.size() != js.size()) {
623                myprintln "* Different number of shapes (${s.size()} != ${js.size()}): ${getDescription(j)}"
624            } else {
625                for(def nums = 0; nums < s.size(); ++nums) {
626                    def ep = s[nums].getPoints()
627                    def jp = js[nums].getPoints()
628                    if(ep.size() != jp.size()) {
629                        myprintln "* Different number of points for shape ${nums+1} (${ep.size()} ! = ${jp.size()})): ${getDescription(j)}"
630                    } else {
631                        for(def nump = 0; nump < ep.size(); ++nump) {
632                            def ept = ep[nump]
633                            def jpt = jp[nump]
634                            if(Math.abs(ept.getLat()-jpt.getLat()) > 0.000001 || Math.abs(ept.getLon()-jpt.getLon()) > 0.000001) {
635                                myprintln "* Different coordinate for point ${nump+1} of shape ${nums+1}: ${getDescription(j)}"
636                                nump = ep.size()
637                                num = s.size()
638                            }
639                        }
640                    }
641                }
642            }
643        }
644        myprintln "*** Mismatching icons: ***"
645        for (def url : eliUrls.keySet()) {
646            def e = eliUrls.get(url)
647            if (!josmUrls.containsKey(url)) {
648                continue
649            }
650            def j = josmUrls.get(url)
651            def ij = getIcon(j)
652            def ie = getIcon(e)
653            if(ij != null && ie == null) {
654                if(!options.nomissingeli) {
655                    myprintln "+ No ELI icon: ${getDescription(j)}"
656                }
657            } else if(ij == null && ie != null) {
658                myprintln "- No JOSM icon: ${getDescription(j)}"
659            } else if(!ij.equals(ie)) {
660                myprintln "* Different icons: ${getDescription(j)}"
661            }
662        }
663        myprintln "*** Miscellaneous checks: ***"
664        def josmIds = new HashMap<String, ImageryInfo>()
665        for (def url : josmUrls.keySet()) {
666            def j = josmUrls.get(url)
667            def id = getId(j)
668            if("wms".equals(getType(j))) {
669                if(!getProjections(j)) {
670                    myprintln "* WMS without projections: ${getDescription(j)}"
671                } else {
672                    def all = Projections.getAllProjectionCodes();
673                    def unsupported = new LinkedList<String>();
674                    for (def p : getProjections(j)) {
675                        if("CRS:84".equals(p)) {
676                            if(!(url =~ /(?i)version=1\.3/)) {
677                                myprintln "! CRS:84 without WMS 1.3: ${getDescription(j)}"
678                            }
679                        } else if(!all.contains(p)) {
680                            unsupported.add(p)
681                        }
682                    }
683                    if (unsupported) {
684                        def s = String.join(" ", unsupported)
685                        myprintln "! Projections ${s} not supported by JOSM (maybe Obsolete code): ${getDescription(j)}"
686                    }
687                }
688                if((url =~ /(?i)version=1\.3/) && !(url =~ /[Cc][Rr][Ss]=\{proj\}/)) {
689                    myprintln "* WMS 1.3 with strange CRS specification: ${getDescription(j)}"
690                }
691                if((url =~ /(?i)version=1\.1/) && !(url =~ /[Ss][Rr][Ss]=\{proj\}/)) {
692                    myprintln "* WMS 1.1 with strange SRS specification: ${getDescription(j)}"
693                }
694            }
695            if(josmMirrors.containsKey(url)) {
696                continue
697            }
698            if(id == null) {
699                myprintln "* No JOSM-ID: ${getDescription(j)}"
700            } else if(josmIds.containsKey(id)) {
701                myprintln "* JOSM-ID ${id} not unique: ${getDescription(j)}"
702            } else {
703                josmIds.put(id, j)
704            }
705            def d = getDate(j)
706            if(!d.isEmpty()) {
707                def reg = (d =~ /^(-|(\d\d\d\d)(-(\d\d)(-(\d\d))?)?)(;(-|(\d\d\d\d)(-(\d\d)(-(\d\d))?)?))?$/)
708                if(reg == null || reg.count != 1) {
709                    myprintln "* JOSM-Date '${d}' is strange: ${getDescription(j)}"
710                } else {
711                    try {
712                        def first = verifyDate(reg[0][2],reg[0][4],reg[0][6])
713                        def second = verifyDate(reg[0][9],reg[0][11],reg[0][13])
714                        if(second.compareTo(first) < 0) {
715                            myprintln "* JOSM-Date '${d}' is strange (second earlier than first): ${getDescription(j)}"
716                        }
717                    }
718                    catch (Exception e) {
719                        myprintln "* JOSM-Date '${d}' is strange (${e.getMessage()}): ${getDescription(j)}"
720                    }
721                }
722            }
723            if(getAttributionUrl(j) && !getAttributionText(j)) {
724                myprintln "* Attribution link without text: ${getDescription(j)}"
725            }
726            if(getLogoUrl(j) && !getLogoImage(j)) {
727                myprintln "* Logo link without image: ${getDescription(j)}"
728            }
729            if(getTermsOfUseText(j) && !getTermsOfUseUrl(j)) {
730                myprintln "* Terms of Use text without link: ${getDescription(j)}"
731            }
732            def js = getShapes(j)
733            if(js.size()) {
734                def minlat = 1000
735                def minlon = 1000
736                def maxlat = -1000
737                def maxlon = -1000
738                for(def s: js) {
739                    for(def p: s.getPoints()) {
740                        def lat = p.getLat()
741                        def lon = p.getLon()
742                        if(lat > maxlat) maxlat = lat
743                        if(lon > maxlon) maxlon = lon
744                        if(lat < minlat) minlat = lat
745                        if(lon < minlon) minlon = lon
746                    }
747                }
748                def b = j.getBounds()
749                if(b.getMinLat() != minlat || b.getMinLon() != minlon || b.getMaxLat() != maxlat || b.getMaxLon() != maxlon) {
750                    myprintln "* Bounds do not match shape (is ${b.getMinLat()},${b.getMinLon()},${b.getMaxLat()},${b.getMaxLon()}, calculated <bounds min-lat='${minlat}' min-lon='${minlon}' max-lat='${maxlat}' max-lon='${maxlon}'>): ${getDescription(j)}"
751                }
752            }
753        }
754    }
755
756    /**
757     * Utility functions that allow uniform access for both ImageryInfo and JsonObject.
758     */
759    static String getUrl(Object e) {
760        if (e instanceof ImageryInfo) return e.url
761        return e.get("properties").getString("url")
762    }
763    static String getUrlStripped(Object e) {
764        return getUrl(e).replaceAll("\\?(apikey|access_token)=.*","")
765    }
766    static String getDate(Object e) {
767        if (e instanceof ImageryInfo) return e.date ? e.date : ""
768        def p = e.get("properties")
769        def start = p.containsKey("start_date") ? p.getString("start_date") : ""
770        def end = p.containsKey("end_date") ? p.getString("end_date") : ""
771        if(!start.isEmpty() && !end.isEmpty())
772            return start+";"+end
773        else if(!start.isEmpty())
774            return start+";-"
775        else if(!end.isEmpty())
776            return "-;"+end
777        return ""
778    }
779    static Date verifyDate(String year, String month, String day) {
780        def date
781        if(year == null) {
782            date = "3000-01-01"
783        } else {
784            date = year + "-" + (month == null ? "01" : month) + "-" + (day == null ? "01" : day)
785        }
786        def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
787        df.setLenient(false)
788        return df.parse(date)
789    }
790    static String getId(Object e) {
791        if (e instanceof ImageryInfo) return e.getId()
792        return e.get("properties").getString("id")
793    }
794    static String getName(Object e) {
795        if (e instanceof ImageryInfo) return e.getOriginalName()
796        return e.get("properties").getString("name")
797    }
798    static List<Object> getMirrors(Object e) {
799        if (e instanceof ImageryInfo) return e.getMirrors()
800        return []
801    }
802    static List<Object> getProjections(Object e) {
803        def r
804        if (e instanceof ImageryInfo) {
805            r = e.getServerProjections()
806        } else {
807            def s = e.get("properties").get("available_projections")
808            if (s) {
809                r = []
810                for (def p : s)
811                    r += p.getString()
812            }
813        }
814        return r ? r : []
815    }
816    static List<Shape> getShapes(Object e) {
817        if (e instanceof ImageryInfo) {
818            def bounds = e.getBounds()
819            if(bounds != null) {
820                return bounds.getShapes()
821            }
822            return []
823        }
824        if(!e.isNull("geometry")) {
825            def ex = e.get("geometry")
826            if(ex != null && !ex.isNull("coordinates")) {
827                def poly = ex.get("coordinates")
828                List<Shape> l = []
829                for(def shapes: poly) {
830                    def s = new Shape()
831                    for(def point: shapes) {
832                        def lon = point[0].toString()
833                        def lat = point[1].toString()
834                        s.addPoint(lat, lon)
835                    }
836                    l.add(s)
837                }
838                return l
839            }
840        }
841        return []
842    }
843    static String getType(Object e) {
844        if (e instanceof ImageryInfo) return e.getImageryType().getTypeString()
845        return e.get("properties").getString("type")
846    }
847    static Integer getMinZoom(Object e) {
848        if (e instanceof ImageryInfo) {
849            if("wms".equals(getType(e)) && e.getName() =~ / mirror/)
850                return null;
851            int mz = e.getMinZoom()
852            return mz == 0 ? null : mz
853        } else {
854            def num = e.get("properties").getJsonNumber("min_zoom")
855            if (num == null) return null
856            return num.intValue()
857        }
858    }
859    static Integer getMaxZoom(Object e) {
860        if (e instanceof ImageryInfo) {
861            if("wms".equals(getType(e)) && e.getName() =~ / mirror/)
862                return null;
863            int mz = e.getMaxZoom()
864            return mz == 0 ? null : mz
865        } else {
866            def num = e.get("properties").getJsonNumber("max_zoom")
867            if (num == null) return null
868            return num.intValue()
869        }
870    }
871    static String getCountryCode(Object e) {
872        if (e instanceof ImageryInfo) return "".equals(e.getCountryCode()) ? null : e.getCountryCode()
873        return e.get("properties").getString("country_code", null)
874    }
875    static String getQuality(Object e) {
876        if (e instanceof ImageryInfo) return e.isBestMarked() ? "eli-best" : null
877        return (e.get("properties").containsKey("best")
878            && e.get("properties").getBoolean("best")) ? "eli-best" : null
879    }
880    static String getIcon(Object e) {
881        if (e instanceof ImageryInfo) return e.getIcon()
882        return e.get("properties").getString("icon", null)
883    }
884    static String getAttributionText(Object e) {
885        if (e instanceof ImageryInfo) return e.getAttributionText(0, null, null)
886        try {return e.get("properties").get("attribution").getString("text", null)} catch (NullPointerException ex) {return null}
887    }
888    static String getAttributionUrl(Object e) {
889        if (e instanceof ImageryInfo) return e.getAttributionLinkURL()
890        try {return e.get("properties").get("attribution").getString("url", null)} catch (NullPointerException ex) {return null}
891    }
892    static String getTermsOfUseText(Object e) {
893        if (e instanceof ImageryInfo) return e.getTermsOfUseText()
894        return null
895    }
896    static String getTermsOfUseUrl(Object e) {
897        if (e instanceof ImageryInfo) return e.getTermsOfUseURL()
898        return null
899    }
900    static String getLogoImage(Object e) {
901        if (e instanceof ImageryInfo) return e.getAttributionImageRaw()
902        return null
903    }
904    static String getLogoUrl(Object e) {
905        if (e instanceof ImageryInfo) return e.getAttributionImageURL()
906        return null
907    }
908    static String getPermissionReferenceUrl(Object e) {
909        if (e instanceof ImageryInfo) return e.getPermissionReferenceURL()
910        return e.get("properties").getString("license_url", null)
911    }
912    static Map<String,String> getDescriptions(Object e) {
913        Map<String,String> res = new HashMap<String, String>()
914        if (e instanceof ImageryInfo) {
915          String a = e.getDescription()
916          if (a) res.put("en", a)
917        } else {
918          String a = e.get("properties").getString("description", null)
919          if (a) res.put("en", a)
920        }
921        return res
922    }
923    static Boolean getValidGeoreference(Object e) {
924        if (e instanceof ImageryInfo) return e.isGeoreferenceValid()
925        return false
926    }
927    static Boolean getDefault(Object e) {
928        if (e instanceof ImageryInfo) return e.isDefaultEntry()
929        return e.get("properties").getBoolean("default", false)
930    }
931    String getDescription(Object o) {
932        def url = getUrl(o)
933        def cc = getCountryCode(o)
934        if (cc == null) {
935            def j = josmUrls.get(url)
936            if (j != null) cc = getCountryCode(j)
937            if (cc == null) {
938                def e = eliUrls.get(url)
939                if (e != null) cc = getCountryCode(e)
940            }
941        }
942        if (cc == null) {
943            cc = ''
944        } else {
945            cc = "[$cc] "
946        }
947        def d = cc + getName(o) + " - " + getUrl(o)
948        if (options.shorten) {
949            def MAXLEN = 140
950            if (d.length() > MAXLEN) d = d.substring(0, MAXLEN-1) + "..."
951        }
952        return d
953    }
954}
Note: See TracBrowser for help on using the repository browser.