Changeset 15033 in josm


Ignore:
Timestamp:
2019-05-02T03:19:26+02:00 (5 years ago)
Author:
Don-vip
Message:

Hasta la vista Groovy

Location:
trunk
Files:
3 deleted
4 edited
1 moved

Legend:

Unmodified
Added
Removed
  • trunk/.classpath

    r14937 r15033  
    8484                </accessrules>
    8585        </classpathentry>
    86         <classpathentry exported="true" kind="con" path="GROOVY_SUPPORT">
    87                 <attributes>
    88                         <attribute name="test" value="true"/>
    89                 </attributes>
    90         </classpathentry>
    9186        <classpathentry kind="lib" path="tools/pmd/commons-lang3-3.8.1.jar">
    92                 <attributes>
    93                         <attribute name="test" value="true"/>
    94                 </attributes>
    95         </classpathentry>
    96         <classpathentry exported="true" kind="con" path="GROOVY_DSL_SUPPORT">
    9787                <attributes>
    9888                        <attribute name="test" value="true"/>
     
    10494                </attributes>
    10595        </classpathentry>
    106         <classpathentry kind="lib" path="tools/groovy/groovy-cli-commons-2.5.5.jar">
    107                 <attributes>
    108                         <attribute name="test" value="true"/>
    109                 </attributes>
    110         </classpathentry>
    111         <classpathentry kind="lib" path="tools/groovy/groovy-xml-2.5.5.jar">
    112                 <attributes>
    113                         <attribute name="test" value="true"/>
    114                 </attributes>
    115         </classpathentry>
    11696        <classpathentry kind="output" path="bin"/>
    11797</classpath>
  • trunk/.project

    r10850 r15033  
    3939        <natures>
    4040                <nature>org.sonarlint.eclipse.core.sonarlintNature</nature>
    41                 <nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
    4241                <nature>org.eclipse.jdt.core.javanature</nature>
    4342                <nature>org.sonar.ide.eclipse.core.sonarNature</nature>
  • trunk/README

    r14646 r15033  
    133133  - optimize-images         short script to decrease size of PNG images
    134134  - since_xxx.py            developer tool to replace "@since xxx" in Javadoc by the upcoming revision number
    135   - SyncEditorLayerIndex.groovy
     135  - SyncEditorLayerIndex.java
    136136                            script to compare and analyse the differences of the editor layer index and the
    137137                            JOSM imagery list (see https://josm.openstreetmap.de/wiki/ImageryCompare)
     
    154154    - checkstyle/           libs and config files for checkstyle (automatically detects code style
    155155                            problems in source code); can be launched as an ant target in build.xml
    156     - commons-cli-1.3.1.jar dependency of Groovy Ant task
    157156    - error_prone_ant.jar   used to detect code errors during compilation
    158157    - spotbugs/             libs and config files for spotbugs (automatically detects common bugs and potential
    159158                            problems in source code); can be launched as an ant target in build.xml
    160     - groovy-all.jar        used for some unit tests and various scripts
    161159    - jacocoant.jar         used to include coverage data into JUnit test reports
    162160    - japicc/               used to generate a compatibility report between optimized jar and normal one
  • trunk/build.xml

    r15032 r15033  
    3838        <property name="mapcss.dir" location="${src.dir}/org/openstreetmap/josm/gui/mappaint/mapcss"/>
    3939        <property name="proj-build.dir" location="${base.dir}/build2"/>
    40         <property name="taginfo-build.dir" location="${base.dir}/build2"/>
     40        <property name="script-build.dir" location="${base.dir}/build2"/>
    4141        <property name="checkstyle-build.dir" location="${base.dir}/build2"/>
    4242        <property name="epsg.output" location="${base.dir}/data/projection/custom-epsg"/>
     
    4747        <property name="failureaccess.jar" location="${tools.dir}/failureaccess.jar"/>
    4848        <property name="guava.jar" location="${tools.dir}/guava.jar"/>
     49        <property name="commons-lang3.jar" location="${pmd.dir}/commons-lang3-3.8.1.jar"/>
    4950        <property name="jformatstring.jar" location="${spotbugs.dir}/jFormatString-3.0.0.jar"/>
    5051        <property name="dist.jar" location="${dist.dir}/josm-custom.jar"/>
     
    7980            <isset property="isJava9"/>
    8081        </condition>
    81         <path id="groovy.classpath">
    82             <fileset dir="${tools.dir}/groovy">
    83                 <include name="*.jar"/>
    84             </fileset>
    85         </path>
    8682        <path id="test.classpath">
    8783            <fileset dir="${test.dir}/lib">
     
    9187            <pathelement path="${failureaccess.jar}"/>
    9288            <pathelement path="${guava.jar}"/>
    93             <pathelement path="${pmd.dir}/commons-lang3-3.8.1.jar"/>
     89            <pathelement path="${commons-lang3.jar}"/>
    9490            <pathelement path="${spotbugs.dir}/spotbugs-annotations.jar"/>
    9591        </path>
     
    430426        <delete dir="${build.dir}"/>
    431427        <delete dir="${proj-build.dir}"/>
     428        <delete dir="${script-build.dir}"/>
    432429        <delete dir="${checkstyle-build.dir}"/>
    433430        <delete dir="${dist.dir}"/>
     
    842839    </target>
    843840
    844     <target name="taginfo-compile" depends="dist">
    845         <javac sourcepath="" srcdir="${base.dir}/scripts" failonerror="true" includes="TagInfoExtract.java"
    846                destdir="${taginfo-build.dir}" target="${java.lang.version}" source="${java.lang.version}" debug="on"
    847                includeantruntime="false" createMissingPackageInfoClass="false"
    848                encoding="UTF-8" classpath="${build.dir}">
     841    <target name="script-compile" depends="dist, test-compile">
     842        <javac sourcepath="" srcdir="${base.dir}/scripts" failonerror="true" includes="*.java"
     843               destdir="${script-build.dir}" target="${java.lang.version}" source="${java.lang.version}" debug="on"
     844               includeantruntime="false" createMissingPackageInfoClass="false" encoding="UTF-8">
     845            <classpath>
     846                <pathelement path="${build.dir}"/>
     847                <pathelement path="${test.dir}/build/unit"/>
     848                <pathelement path="${guava.jar}"/>
     849                <pathelement path="${commons-lang3.jar}"/>
     850            </classpath>
    849851        </javac>
    850852    </target>
     
    859861                <classpath>
    860862                    <pathelement path="${dist.jar}"/>
    861                     <pathelement path="${taginfo-build.dir}"/>
     863                    <pathelement path="${script-build.dir}"/>
     864                    <pathelement path="${guava.jar}"/>
     865                    <pathelement path="${commons-lang3.jar}"/>
    862866                </classpath>
    863867                <arg value="--type"/>
     
    872876    </macrodef>
    873877
    874     <target name="taginfo" depends="taginfo-compile">
     878    <target name="taginfo" depends="script-compile">
    875879        <_taginfo type="mappaint" output="taginfo_style.json"/>
    876880        <_taginfo type="presets" output="taginfo_presets.json"/>
     
    878882    </target>
    879883
    880     <target name="imageryindex" depends="init-properties">
    881         <taskdef name="groovy" classname="org.codehaus.groovy.ant.Groovy" classpathref="groovy.classpath"/>
     884    <target name="imageryindex" depends="init-properties,script-compile">
    882885        <echo message="Checking editor imagery difference"/>
    883         <groovy src="scripts/SyncEditorLayerIndex.groovy" classpath="${dist.jar}">
    884                 <arg value="-noeli"/>
    885                 <arg value="-p"/>
    886                 <arg value="imagery_eliout.imagery.xml"/>
    887                 <arg value="-q"/>
    888                 <arg value="imagery_josmout.imagery.xml"/>
    889         </groovy>
     886        <java classname="SyncEditorLayerIndex" failonerror="true" fork="false">
     887            <classpath>
     888                <pathelement path="${dist.jar}"/>
     889                <pathelement path="${script-build.dir}"/>
     890                <pathelement path="${guava.jar}"/>
     891                <pathelement path="${commons-lang3.jar}"/>
     892            </classpath>
     893            <arg value="--noeli"/>
     894            <arg value="-p"/>
     895            <arg value="imagery_eliout.imagery.xml"/>
     896            <arg value="-q"/>
     897            <arg value="imagery_josmout.imagery.xml"/>
     898        </java>
    890899    </target>
    891900
  • trunk/scripts/SyncEditorLayerIndex.java

    r15001 r15033  
    11// License: GPL. For details, see LICENSE file.
     2import static org.apache.commons.lang3.StringUtils.isBlank;
     3import static org.apache.commons.lang3.StringUtils.isNotBlank;
     4
     5import java.io.BufferedReader;
     6import java.io.FileInputStream;
     7import java.io.FileNotFoundException;
     8import java.io.FileOutputStream;
     9import java.io.IOException;
     10import java.io.InputStreamReader;
     11import java.io.OutputStreamWriter;
     12import java.io.UnsupportedEncodingException;
     13import java.lang.reflect.Field;
     14import java.text.DecimalFormat;
     15import java.text.ParseException;
     16import java.text.SimpleDateFormat;
     17import java.util.ArrayList;
     18import java.util.Arrays;
     19import java.util.Calendar;
     20import java.util.Collection;
     21import java.util.Collections;
     22import java.util.Date;
     23import java.util.HashMap;
     24import java.util.LinkedList;
     25import java.util.List;
     26import java.util.Locale;
     27import java.util.Map;
     28import java.util.Map.Entry;
     29import java.util.Objects;
     30import java.util.regex.Matcher;
     31import java.util.regex.Pattern;
     32import java.util.stream.Collectors;
     33
     34import javax.json.Json;
     35import javax.json.JsonArray;
     36import javax.json.JsonNumber;
     37import javax.json.JsonObject;
     38import javax.json.JsonReader;
     39import javax.json.JsonString;
     40import javax.json.JsonValue;
     41
     42import org.openstreetmap.gui.jmapviewer.Coordinate;
     43import org.openstreetmap.josm.data.Preferences;
     44import org.openstreetmap.josm.data.imagery.ImageryInfo;
     45import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
     46import org.openstreetmap.josm.data.imagery.Shape;
     47import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
     48import org.openstreetmap.josm.data.projection.Projections;
     49import org.openstreetmap.josm.data.validation.routines.DomainValidator;
     50import org.openstreetmap.josm.io.imagery.ImageryReader;
     51import org.openstreetmap.josm.spi.preferences.Config;
     52import org.openstreetmap.josm.tools.OptionParser;
     53import org.openstreetmap.josm.tools.OptionParser.OptionCount;
     54import org.openstreetmap.josm.tools.ReflectionUtils;
     55import org.xml.sax.SAXException;
     56
    257/**
    358 * Compare and analyse the differences of the editor layer index and the JOSM imagery list.
     
    1368 * Main JOSM binary needs to be in classpath, e.g.
    1469 *
    15  * $ groovy -cp ../dist/josm-custom.jar SyncEditorLayerIndex.groovy
     70 * $ java -cp ../dist/josm-custom.jar SyncEditorLayerIndex
    1671 *
    1772 * Add option "-h" to show the available command line flags.
    1873 */
    19 import java.text.DecimalFormat
    20 
    21 import javax.json.Json
    22 import javax.json.JsonArray
    23 import javax.json.JsonObject
    24 import javax.json.JsonReader
    25 import javax.json.JsonValue
    26 
    27 import org.openstreetmap.josm.data.Preferences
    28 import org.openstreetmap.josm.data.imagery.ImageryInfo
    29 import org.openstreetmap.josm.data.imagery.Shape
    30 import org.openstreetmap.josm.data.preferences.JosmBaseDirectories
    31 import org.openstreetmap.josm.data.projection.Projections
    32 import org.openstreetmap.josm.data.validation.routines.DomainValidator
    33 import org.openstreetmap.josm.io.imagery.ImageryReader
    34 import org.openstreetmap.josm.spi.preferences.Config
    35 
    36 class SyncEditorLayerIndex {
    37 
    38     List<ImageryInfo> josmEntries
    39     JsonArray eliEntries
    40 
    41     def eliUrls = new HashMap<String, JsonObject>()
    42     def josmUrls = new HashMap<String, ImageryInfo>()
    43     def josmMirrors = new HashMap<String, ImageryInfo>()
    44     static def oldproj = new HashMap<String, String>()
    45     static def ignoreproj = new LinkedList<String>()
    46 
    47     static String eliInputFile = 'imagery_eli.geojson'
    48     static String josmInputFile = 'imagery_josm.imagery.xml'
    49     static String ignoreInputFile = 'imagery_josm.ignores.txt'
    50     static FileOutputStream outputFile = null
    51     static OutputStreamWriter outputStream = null
    52     def skip = [:]
    53 
    54     static def options
     74public class SyncEditorLayerIndex {
     75
     76    private List<ImageryInfo> josmEntries;
     77    private JsonArray eliEntries;
     78
     79    private Map<String, JsonObject> eliUrls = new HashMap<>();
     80    private Map<String, ImageryInfo> josmUrls = new HashMap<>();
     81    private Map<String, ImageryInfo> josmMirrors = new HashMap<>();
     82    private static Map<String, String> oldproj = new HashMap<>();
     83    private static List<String> ignoreproj = new LinkedList<>();
     84
     85    private static String eliInputFile = "imagery_eli.geojson";
     86    private static String josmInputFile = "imagery_josm.imagery.xml";
     87    private static String ignoreInputFile = "imagery_josm.ignores.txt";
     88    private static FileOutputStream outputFile = null;
     89    private static OutputStreamWriter outputStream = null;
     90    private static String optionOutput;
     91    private static boolean optionShorten;
     92    private static boolean optionNoSkip;
     93    private static boolean optionXhtmlBody;
     94    private static boolean optionXhtml;
     95    private static String optionEliXml;
     96    private static String optionJosmXml;
     97    private static String optionEncoding;
     98    private static boolean optionNoEli;
     99    private Map<String, String> skip = new HashMap<>();
    55100
    56101    /**
    57102     * Main method.
     103     * @param args program arguments
     104     * @throws IOException if any I/O error occurs
     105     * @throws ReflectiveOperationException if any reflective operation error occurs
     106     * @throws SAXException if any SAX error occurs
    58107     */
    59     static main(def args) {
    60         Locale.setDefault(Locale.ROOT)
    61         parse_command_line_arguments(args)
    62         def pref = new Preferences(JosmBaseDirectories.getInstance())
    63         Config.setPreferencesInstance(pref)
    64         pref.init(false)
    65         def script = new SyncEditorLayerIndex()
    66         script.setupProj()
    67         script.loadSkip()
    68         script.start()
    69         script.loadJosmEntries()
    70         if(options.josmxml) {
    71             def file = new FileOutputStream(options.josmxml)
    72             def stream = new OutputStreamWriter(file, "UTF-8")
    73             script.printentries(script.josmEntries, stream)
    74             stream.close()
    75             file.close()
    76         }
    77         script.loadELIEntries()
    78         if(options.elixml) {
    79             def file = new FileOutputStream(options.elixml)
    80             def stream = new OutputStreamWriter(file, "UTF-8")
    81             script.printentries(script.eliEntries, stream)
    82             stream.close()
    83             file.close()
    84         }
    85         script.checkInOneButNotTheOther()
    86         script.checkCommonEntries()
    87         script.end()
    88         if(outputStream != null) {
    89             outputStream.close()
    90         }
    91         if(outputFile != null) {
    92             outputFile.close()
    93         }
     108    public static void main(String[] args) throws IOException, SAXException, ReflectiveOperationException {
     109        Locale.setDefault(Locale.ROOT);
     110        parse_command_line_arguments(args);
     111        Preferences pref = new Preferences(JosmBaseDirectories.getInstance());
     112        Config.setPreferencesInstance(pref);
     113        pref.init(false);
     114        SyncEditorLayerIndex script = new SyncEditorLayerIndex();
     115        script.setupProj();
     116        script.loadSkip();
     117        script.start();
     118        script.loadJosmEntries();
     119        if (optionJosmXml != null) {
     120            FileOutputStream file = new FileOutputStream(optionJosmXml);
     121            OutputStreamWriter stream = new OutputStreamWriter(file, "UTF-8");
     122            script.printentries(script.josmEntries, stream);
     123            stream.close();
     124            file.close();
     125        }
     126        script.loadELIEntries();
     127        if (optionEliXml != null) {
     128            FileOutputStream file = new FileOutputStream(optionEliXml);
     129            OutputStreamWriter stream = new OutputStreamWriter(file, "UTF-8");
     130            script.printentries(script.eliEntries, stream);
     131            stream.close();
     132            file.close();
     133        }
     134        script.checkInOneButNotTheOther();
     135        script.checkCommonEntries();
     136        script.end();
     137        if (outputStream != null) {
     138            outputStream.close();
     139        }
     140        if (outputFile != null) {
     141            outputFile.close();
     142        }
     143    }
     144
     145    /**
     146     * Displays help on the console
     147     */
     148    private static void showHelp() {
     149        System.out.println(getHelp());
     150        System.exit(0);
     151    }
     152
     153    static String getHelp() {
     154        return "usage: java -cp build SyncEditorLayerIndex\n" +
     155        "-c,--encoding <encoding>           output encoding (defaults to UTF-8 or cp850 on Windows)\n" +
     156        "-e,--eli_input <eli_input>         Input file for the editor layer index (geojson). Default is imagery_eli.geojson (current directory).\n" +
     157        "-h,--help                          show this help\n" +
     158        "-i,--ignore_input <ignore_input>   Input file for the ignore list. Default is imagery_josm.ignores.txt (current directory).\n" +
     159        "-j,--josm_input <josm_input>       Input file for the JOSM imagery list (xml). Default is imagery_josm.imagery.xml (current directory).\n" +
     160        "-m,--noeli                         don't show output for ELI problems\n" +
     161        "-n,--noskip                        don't skip known entries\n" +
     162        "-o,--output <output>               Output file, - prints to stdout (default: -)\n" +
     163        "-p,--elixml <elixml>               ELI entries for use in JOSM as XML file (incomplete)\n" +
     164        "-q,--josmxml <josmxml>             JOSM entries reoutput as XML file (incomplete)\n" +
     165        "-s,--shorten                       shorten the output, so it is easier to read in a console window\n" +
     166        "-x,--xhtmlbody                     create XHTML body for display in a web page\n" +
     167        "-X,--xhtml                         create XHTML for display in a web page\n";
    94168    }
    95169
    96170    /**
    97171     * Parse command line arguments.
     172     * @param args program arguments
     173     * @throws FileNotFoundException if a file can't be found
     174     * @throws UnsupportedEncodingException  if the given encoding can't be found
    98175     */
    99     static void parse_command_line_arguments(args) {
    100         def cli = new CliBuilder(width: 160)
    101         cli.o(longOpt:'output', args:1, argName: "output", "Output file, - prints to stdout (default: -)")
    102         cli.e(longOpt:'eli_input', args:1, argName:"eli_input", "Input file for the editor layer index (geojson). Default is $eliInputFile (current directory).")
    103         cli.j(longOpt:'josm_input', args:1, argName:"josm_input", "Input file for the JOSM imagery list (xml). Default is $josmInputFile (current directory).")
    104         cli.i(longOpt:'ignore_input', args:1, argName:"ignore_input", "Input file for the ignore list. Default is $ignoreInputFile (current directory).")
    105         cli.s(longOpt:'shorten', "shorten the output, so it is easier to read in a console window")
    106         cli.n(longOpt:'noskip', argName:"noskip", "don't skip known entries")
    107         cli.x(longOpt:'xhtmlbody', argName:"xhtmlbody", "create XHTML body for display in a web page")
    108         cli.X(longOpt:'xhtml', argName:"xhtml", "create XHTML for display in a web page")
    109         cli.p(longOpt:'elixml', args:1, argName:"elixml", "ELI entries for use in JOSM as XML file (incomplete)")
    110         cli.q(longOpt:'josmxml', args:1, argName:"josmxml", "JOSM entries reoutput as XML file (incomplete)")
    111         cli.m(longOpt:'noeli', argName:"noeli", "don't show output for ELI problems")
    112         cli.c(longOpt:'encoding', args:1, argName:"encoding", "output encoding (defaults to UTF-8 or cp850 on Windows)")
    113         cli.h(longOpt:'help', "show this help")
    114         options = cli.parse(args)
    115 
    116         if (options.h) {
    117             cli.usage()
    118             System.exit(0)
    119         }
    120         if (options.eli_input) {
    121             eliInputFile = options.eli_input
    122         }
    123         if (options.josm_input) {
    124             josmInputFile = options.josm_input
    125         }
    126         if (options.ignore_input) {
    127             ignoreInputFile = options.ignore_input
    128         }
    129         if (options.output && options.output != "-") {
    130             outputFile = new FileOutputStream(options.output)
    131             outputStream = new OutputStreamWriter(outputFile, options.encoding ? options.encoding : "UTF-8")
    132         } else if (options.encoding) {
    133             outputStream = new OutputStreamWriter(System.out, options.encoding)
     176    static void parse_command_line_arguments(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
     177        new OptionParser("JOSM/ELI synchronization script")
     178                .addFlagParameter("help", SyncEditorLayerIndex::showHelp)
     179                .addShortAlias("help", "h")
     180                .addArgumentParameter("output", OptionCount.OPTIONAL, x -> optionOutput = x)
     181                .addShortAlias("output", "o")
     182                .addArgumentParameter("eli_input", OptionCount.OPTIONAL, x -> eliInputFile = x)
     183                .addShortAlias("eli_input", "e")
     184                .addArgumentParameter("josm_input", OptionCount.OPTIONAL, x -> josmInputFile = x)
     185                .addShortAlias("josm_input", "j")
     186                .addArgumentParameter("ignore_input", OptionCount.OPTIONAL, x -> ignoreInputFile = x)
     187                .addShortAlias("ignore_input", "i")
     188                .addFlagParameter("shorten", () -> optionShorten = true)
     189                .addShortAlias("shorten", "s")
     190                .addFlagParameter("noskip", () -> optionNoSkip = true)
     191                .addShortAlias("noskip", "n")
     192                .addFlagParameter("xhtmlbody", () -> optionXhtmlBody = true)
     193                .addShortAlias("xhtmlbody", "x")
     194                .addFlagParameter("xhtml", () -> optionXhtml = true)
     195                .addShortAlias("xhtml", "X")
     196                .addArgumentParameter("elixml", OptionCount.OPTIONAL, x -> optionEliXml = x)
     197                .addShortAlias("elixml", "p")
     198                .addArgumentParameter("josmxml", OptionCount.OPTIONAL, x -> optionJosmXml = x)
     199                .addShortAlias("josmxml", "q")
     200                .addFlagParameter("noeli", () -> optionNoEli = true)
     201                .addShortAlias("noeli", "m")
     202                .addArgumentParameter("encoding", OptionCount.OPTIONAL, x -> optionEncoding = x)
     203                .addShortAlias("encoding", "c")
     204                .parseOptionsOrExit(Arrays.asList(args));
     205
     206        if (optionOutput != null && optionOutput != "-") {
     207            outputFile = new FileOutputStream(optionOutput);
     208            outputStream = new OutputStreamWriter(outputFile, optionEncoding != null ? optionEncoding : "UTF-8");
     209        } else if (optionEncoding != null) {
     210            outputStream = new OutputStreamWriter(System.out, optionEncoding);
    134211        }
    135212    }
    136213
    137214    void setupProj() {
    138         oldproj.put("EPSG:3359", "EPSG:3404")
    139         oldproj.put("EPSG:3785", "EPSG:3857")
    140         oldproj.put("EPSG:31297", "EPGS:31287")
    141         oldproj.put("EPSG:31464", "EPSG:31468")
    142         oldproj.put("EPSG:54004", "EPSG:3857")
    143         oldproj.put("EPSG:102100", "EPSG:3857")
    144         oldproj.put("EPSG:102113", "EPSG:3857")
    145         oldproj.put("EPSG:900913", "EPGS:3857")
    146         ignoreproj.add("EPSG:4267")
    147         ignoreproj.add("EPSG:5221")
    148         ignoreproj.add("EPSG:5514")
    149         ignoreproj.add("EPSG:32019")
    150         ignoreproj.add("EPSG:102066")
    151         ignoreproj.add("EPSG:102067")
    152         ignoreproj.add("EPSG:102685")
    153         ignoreproj.add("EPSG:102711")
    154     }
    155 
    156     void loadSkip() {
    157         def fr = new InputStreamReader(new FileInputStream(ignoreInputFile), "UTF-8")
    158         def line
    159 
    160         while((line = fr.readLine()) != null) {
    161             def res = (line =~ /^\|\| *(ELI|Ignore) *\|\| *\{\{\{(.+)\}\}\} *\|\|/)
    162             if(res.count)
    163             {
    164                 if(res[0][1].equals("Ignore")) {
    165                     skip[res[0][2]] = "green"
    166                 } else {
    167                     skip[res[0][2]] = "darkgoldenrod"
    168                 }
    169             }
    170         }
    171     }
    172 
    173     void myprintlnfinal(String s) {
    174         if(outputStream != null) {
    175             outputStream.write(s+System.getProperty("line.separator"))
     215        oldproj.put("EPSG:3359", "EPSG:3404");
     216        oldproj.put("EPSG:3785", "EPSG:3857");
     217        oldproj.put("EPSG:31297", "EPGS:31287");
     218        oldproj.put("EPSG:31464", "EPSG:31468");
     219        oldproj.put("EPSG:54004", "EPSG:3857");
     220        oldproj.put("EPSG:102100", "EPSG:3857");
     221        oldproj.put("EPSG:102113", "EPSG:3857");
     222        oldproj.put("EPSG:900913", "EPGS:3857");
     223        ignoreproj.add("EPSG:4267");
     224        ignoreproj.add("EPSG:5221");
     225        ignoreproj.add("EPSG:5514");
     226        ignoreproj.add("EPSG:32019");
     227        ignoreproj.add("EPSG:102066");
     228        ignoreproj.add("EPSG:102067");
     229        ignoreproj.add("EPSG:102685");
     230        ignoreproj.add("EPSG:102711");
     231    }
     232
     233    void loadSkip() throws IOException {
     234        final Pattern pattern = Pattern.compile("^\\|\\| *(ELI|Ignore) *\\|\\| *\\{\\{\\{(.+)\\}\\}\\} *\\|\\|");
     235        try (BufferedReader fr = new BufferedReader(new InputStreamReader(new FileInputStream(ignoreInputFile), "UTF-8"))) {
     236            String line;
     237
     238            while ((line = fr.readLine()) != null) {
     239                Matcher res = pattern.matcher(line);
     240                if (res.matches()) {
     241                    if ("Ignore".equals(res.group(1))) {
     242                        skip.put(res.group(2), "green");
     243                    } else {
     244                        skip.put(res.group(2), "darkgoldenrod");
     245                    }
     246                }
     247            }
     248        }
     249    }
     250
     251    void myprintlnfinal(String s) throws IOException {
     252        if (outputStream != null) {
     253            outputStream.write(s + System.getProperty("line.separator"));
    176254        } else {
    177             println s
    178         }
    179     }
    180 
    181     void myprintln(String s) {
    182         if(skip.containsKey(s)) {
    183             String color = skip.get(s)
    184             skip.remove(s)
    185             if(options.xhtmlbody || options.xhtml) {
    186                 s = "<pre style=\"margin:3px;color:"+color+"\">"+s.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")+"</pre>"
    187             }
    188             if (!options.noskip) {
    189                 return
    190             }
    191         } else if(options.xhtmlbody || options.xhtml) {
    192             String color = s.startsWith("***") ? "black" : ((s.startsWith("+ ") || s.startsWith("+++ ELI")) ? "blue" :
    193             (s.startsWith("#") ? "indigo" : (s.startsWith("!") ? "orange" : "red")))
    194             s = "<pre style=\"margin:3px;color:"+color+"\">"+s.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")+"</pre>"
    195         }
    196         if ((s.startsWith("+ ") || s.startsWith("+++ ELI") || s.startsWith("#")) && options.noeli) {
    197             return
    198         }
    199         myprintlnfinal(s)
    200     }
    201 
    202     void start() {
    203         if (options.xhtml) {
    204             myprintlnfinal "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
    205             myprintlnfinal "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/><title>JOSM - ELI differences</title></head><body>\n"
    206         }
    207     }
    208 
    209     void end() {
    210         for (def s: skip.keySet()) {
    211             myprintln "+++ Obsolete skip entry: " + s
    212         }
    213         if (options.xhtml) {
    214             myprintlnfinal "</body></html>\n"
    215         }
    216     }
    217 
    218     void loadELIEntries() {
    219         def fr = new InputStreamReader(new FileInputStream(eliInputFile), "UTF-8")
    220         JsonReader jr = Json.createReader(fr)
    221         eliEntries = jr.readObject().get("features")
    222         jr.close()
    223 
    224         for (def e : eliEntries) {
    225             def url = getUrlStripped(e)
     255            System.out.println(s);
     256        }
     257    }
     258
     259    void myprintln(String s) throws IOException {
     260        if (skip.containsKey(s)) {
     261            String color = skip.get(s);
     262            skip.remove(s);
     263            if (optionXhtmlBody || optionXhtml) {
     264                s = "<pre style=\"margin:3px;color:"+color+"\">"+s.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")+"</pre>";
     265            }
     266            if (!optionNoSkip) {
     267                return;
     268            }
     269        } else if(optionXhtmlBody || optionXhtml) {
     270            String color =
     271                    s.startsWith("***") ? "black" :
     272                        ((s.startsWith("+ ") || s.startsWith("+++ ELI")) ? "blue" :
     273                            (s.startsWith("#") ? "indigo" :
     274                                (s.startsWith("!") ? "orange" : "red")));
     275            s = "<pre style=\"margin:3px;color:"+color+"\">"+s.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")+"</pre>";
     276        }
     277        if ((s.startsWith("+ ") || s.startsWith("+++ ELI") || s.startsWith("#")) && optionNoEli) {
     278            return;
     279        }
     280        myprintlnfinal(s);
     281    }
     282
     283    void start() throws IOException {
     284        if (optionXhtml) {
     285            myprintlnfinal("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n");
     286            myprintlnfinal("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/><title>JOSM - ELI differences</title></head><body>\n");
     287        }
     288    }
     289
     290    void end() throws IOException {
     291        for (String s : skip.keySet()) {
     292            myprintln("+++ Obsolete skip entry: " + s);
     293        }
     294        if (optionXhtml) {
     295            myprintlnfinal("</body></html>\n");
     296        }
     297    }
     298
     299    void loadELIEntries() throws IOException {
     300        try (JsonReader jr = Json.createReader(new InputStreamReader(new FileInputStream(eliInputFile), "UTF-8"))) {
     301            eliEntries = jr.readObject().getJsonArray("features");
     302        }
     303
     304        for (JsonValue e : eliEntries) {
     305            String url = getUrlStripped(e);
    226306            if (url.contains("{z}")) {
    227                 myprintln "+++ ELI-URL uses {z} instead of {zoom}: "+url
    228                 url = url.replace("{z}","{zoom}")
     307                myprintln("+++ ELI-URL uses {z} instead of {zoom}: "+url);
     308                url = url.replace("{z}","{zoom}");
    229309            }
    230310            if (eliUrls.containsKey(url)) {
    231                 myprintln "+++ ELI-URL is not unique: "+url
     311                myprintln("+++ ELI-URL is not unique: "+url);
    232312            } else {
    233                 eliUrls.put(url, e)
    234             }
    235             def s = e.get("properties").get("available_projections")
    236             if (s) {
    237                 def old = new LinkedList<String>()
    238                 for (def p : s) {
    239                     def proj = p.getString()
    240                     if(oldproj.containsKey(proj) || ("CRS:84".equals(proj) && !(url =~ /(?i)version=1\.3/))) {
    241                         old.add(proj)
    242                     }
    243                 }
    244                 if (old) {
    245                     def str = String.join(", ", old)
    246                     myprintln "+ ELI Projections ${str} not useful: ${getDescription(e)}"
    247                 }
    248             }
    249         }
    250         myprintln "*** Loaded ${eliEntries.size()} entries (ELI). ***"
    251     }
    252     String cdata(def s, boolean escape = false) {
    253         if(escape) {
    254             return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;")
    255         } else if(s =~ /[<>&]/)
    256             return "<![CDATA[$s]]>"
    257        return s
    258     }
    259 
    260     String maininfo(def entry, String offset) {
    261         String t = getType(entry)
    262         String res = offset + "<type>$t</type>\n"
    263         res += offset + "<url>${cdata(getUrl(entry))}</url>\n"
    264         if(getMinZoom(entry) != null)
    265             res += offset + "<min-zoom>${getMinZoom(entry)}</min-zoom>\n"
    266         if(getMaxZoom(entry) != null)
    267             res += offset + "<max-zoom>${getMaxZoom(entry)}</max-zoom>\n"
    268         if (t == "wms") {
    269             def p = getProjections(entry)
    270             if (p) {
    271                 res += offset + "<projections>\n"
    272                 for (def c : p)
    273                     res += offset + "    <code>$c</code>\n"
    274                 res += offset + "</projections>\n"
    275             }
    276         }
    277         return res
    278     }
    279 
    280     void printentries(def entries, def stream) {
    281         DecimalFormat df = new DecimalFormat("#.#######")
    282         df.setRoundingMode(java.math.RoundingMode.CEILING)
    283         stream.write "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
    284         stream.write "<imagery xmlns=\"http://josm.openstreetmap.de/maps-1.0\">\n"
    285         for (def e : entries) {
     313                eliUrls.put(url, e.asJsonObject());
     314            }
     315            JsonArray s = e.asJsonObject().get("properties").asJsonObject().getJsonArray("available_projections");
     316            if (s != null) {
     317                String urlLc = url.toLowerCase(Locale.ENGLISH);
     318                List<String> old = new LinkedList<>();
     319                for (JsonValue p : s) {
     320                    String proj = ((JsonString) p).getString();
     321                    if (oldproj.containsKey(proj) || ("CRS:84".equals(proj) && !urlLc.contains("version=1.3"))) {
     322                        old.add(proj);
     323                    }
     324                }
     325                if (!old.isEmpty()) {
     326                    myprintln("+ ELI Projections "+String.join(", ", old)+" not useful: "+getDescription(e));
     327                }
     328            }
     329        }
     330        myprintln("*** Loaded "+eliEntries.size()+" entries (ELI). ***");
     331    }
     332
     333    String cdata(String s) {
     334        return cdata(s, false);
     335    }
     336
     337    String cdata(String s, boolean escape) {
     338        if (escape) {
     339            return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
     340        } else if (s.matches("[<>&]"))
     341            return "<![CDATA["+s+"]]>";
     342        return s;
     343    }
     344
     345    String maininfo(Object entry, String offset) {
     346        String t = getType(entry);
     347        String res = offset + "<type>"+t+"</type>\n";
     348        res += offset + "<url>"+cdata(getUrl(entry))+"</url>\n";
     349        if (getMinZoom(entry) != null)
     350            res += offset + "<min-zoom>"+getMinZoom(entry)+"</min-zoom>\n";
     351        if (getMaxZoom(entry) != null)
     352            res += offset + "<max-zoom>"+getMaxZoom(entry)+"</max-zoom>\n";
     353        if ("wms".equals(t)) {
     354            List<String> p = getProjections(entry);
     355            if (p != null) {
     356                res += offset + "<projections>\n";
     357                for (String c : p)
     358                    res += offset + "    <code>"+c+"</code>\n";
     359                res += offset + "</projections>\n";
     360            }
     361        }
     362        return res;
     363    }
     364
     365    void printentries(List<?> entries, OutputStreamWriter stream) throws IOException {
     366        DecimalFormat df = new DecimalFormat("#.#######");
     367        df.setRoundingMode(java.math.RoundingMode.CEILING);
     368        stream.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
     369        stream.write("<imagery xmlns=\"http://josm.openstreetmap.de/maps-1.0\">\n");
     370        for (Object e : entries) {
    286371            stream.write("    <entry"
    287372                + ("eli-best".equals(getQuality(e)) ? " eli-best=\"true\"" : "" )
    288373                + (getOverlay(e) ? " overlay=\"true\"" : "" )
    289                 + ">\n")
    290             def t
    291             if((t = getName(e)))
    292                 stream.write "        <name>${cdata(t, true)}</name>\n"
    293             if((t = getId(e)))
    294                 stream.write "        <id>$t</id>\n"
    295             if((t = getCategory(e)))
    296                 stream.write "        <category>$t</category>\n"
    297             if((t = getDate(e)))
    298                 stream.write "        <date>$t</date>\n"
    299             if((t = getCountryCode(e)))
    300                 stream.write "        <country-code>$t</country-code>\n"
    301             if((getDefault(e)))
    302                 stream.write "        <default>true</default>\n"
    303             stream.write maininfo(e, "        ")
    304             if((t = getAttributionText(e)))
    305                 stream.write "        <attribution-text mandatory=\"true\">${cdata(t, true)}</attribution-text>\n"
    306             if((t = getAttributionUrl(e)))
    307                 stream.write "        <attribution-url>${cdata(t)}</attribution-url>\n"
    308             if((t = getLogoImage(e)))
    309                 stream.write "        <logo-image>${cdata(t, true)}</logo-image>\n"
    310             if((t = getLogoUrl(e)))
    311                 stream.write "        <logo-url>${cdata(t)}</logo-url>\n"
    312             if((t = getTermsOfUseText(e)))
    313                 stream.write "        <terms-of-use-text>${cdata(t, true)}</terms-of-use-text>\n"
    314             if((t = getTermsOfUseUrl(e)))
    315                 stream.write "        <terms-of-use-url>${cdata(t)}</terms-of-use-url>\n"
    316             if((t = getPermissionReferenceUrl(e)))
    317                 stream.write "        <permission-ref>${cdata(t)}</permission-ref>\n"
    318             if((getValidGeoreference(e)))
    319                 stream.write "        <valid-georeference>true</valid-georeference>\n"
    320             if((t = getIcon(e)))
    321                 stream.write "        <icon>${cdata(t)}</icon>\n"
    322             for (def d : getDescriptions(e)) {
    323                     stream.write "        <description lang=\"${d.getKey()}\">${d.getValue()}</description>\n"
    324             }
    325             for (def m : getMirrors(e)) {
    326                     stream.write "        <mirror>\n"+maininfo(m, "            ")+"        </mirror>\n"
    327             }
    328             def minlat = 1000
    329             def minlon = 1000
    330             def maxlat = -1000
    331             def maxlon = -1000
    332             def shapes = ""
    333             def sep = "\n            "
     374                + ">\n");
     375            String t;
     376            if (isNotBlank(t = getName(e)))
     377                stream.write("        <name>"+cdata(t, true)+"</name>\n");
     378            if (isNotBlank(t = getId(e)))
     379                stream.write("        <id>"+t+"</id>\n");
     380            if (isNotBlank(t = getCategory(e)))
     381                stream.write("        <category>"+t+"</category>\n");
     382            if (isNotBlank(t = getDate(e)))
     383                stream.write("        <date>"+t+"</date>\n");
     384            if (isNotBlank(t = getCountryCode(e)))
     385                stream.write("        <country-code>"+t+"</country-code>\n");
     386            if ((getDefault(e)))
     387                stream.write("        <default>true</default>\n");
     388            stream.write(maininfo(e, "        "));
     389            if (isNotBlank(t = getAttributionText(e)))
     390                stream.write("        <attribution-text mandatory=\"true\">"+cdata(t, true)+"</attribution-text>\n");
     391            if (isNotBlank(t = getAttributionUrl(e)))
     392                stream.write("        <attribution-url>"+cdata(t)+"</attribution-url>\n");
     393            if (isNotBlank(t = getLogoImage(e)))
     394                stream.write("        <logo-image>"+cdata(t, true)+"</logo-image>\n");
     395            if (isNotBlank(t = getLogoUrl(e)))
     396                stream.write("        <logo-url>"+cdata(t)+"</logo-url>\n");
     397            if (isNotBlank(t = getTermsOfUseText(e)))
     398                stream.write("        <terms-of-use-text>"+cdata(t, true)+"</terms-of-use-text>\n");
     399            if (isNotBlank(t = getTermsOfUseUrl(e)))
     400                stream.write("        <terms-of-use-url>"+cdata(t)+"</terms-of-use-url>\n");
     401            if (isNotBlank(t = getPermissionReferenceUrl(e)))
     402                stream.write("        <permission-ref>"+cdata(t)+"</permission-ref>\n");
     403            if ((getValidGeoreference(e)))
     404                stream.write("        <valid-georeference>true</valid-georeference>\n");
     405            if (isNotBlank(t = getIcon(e)))
     406                stream.write("        <icon>"+cdata(t)+"</icon>\n");
     407            for (Entry<String, String> d : getDescriptions(e).entrySet()) {
     408                stream.write("        <description lang=\""+d.getKey()+"\">"+d.getValue()+"</description>\n");
     409            }
     410            for (ImageryInfo m : getMirrors(e)) {
     411                stream.write("        <mirror>\n"+maininfo(m, "            ")+"        </mirror>\n");
     412            }
     413            double minlat = 1000;
     414            double minlon = 1000;
     415            double maxlat = -1000;
     416            double maxlon = -1000;
     417            String shapes = "";
     418            String sep = "\n            ";
    334419            try {
    335                 for(def s: getShapes(e)) {
    336                     shapes += "            <shape>"
    337                     def i = 0
    338                     for(def p: s.getPoints()) {
    339                         def lat = p.getLat()
    340                         def lon = p.getLon()
    341                         if(lat > maxlat) maxlat = lat
    342                         if(lon > maxlon) maxlon = lon
    343                         if(lat < minlat) minlat = lat
    344                         if(lon < minlon) minlon = lon
    345                         if(!(i++%3)) {
    346                             shapes += sep + "    "
     420                for (Shape s: getShapes(e)) {
     421                    shapes += "            <shape>";
     422                    int i = 0;
     423                    for (Coordinate p: s.getPoints()) {
     424                        double lat = p.getLat();
     425                        double lon = p.getLon();
     426                        if (lat > maxlat) maxlat = lat;
     427                        if (lon > maxlon) maxlon = lon;
     428                        if (lat < minlat) minlat = lat;
     429                        if (lon < minlon) minlon = lon;
     430                        if ((i++%3) == 0) {
     431                            shapes += sep + "    ";
    347432                        }
    348                         shapes += "<point lat='${df.format(lat)}' lon='${df.format(lon)}'/>"
    349                     }
    350                     shapes += sep + "</shape>\n"
     433                        shapes += "<point lat='"+df.format(lat)+"' lon='"+df.format(lon)+"'/>";
     434                    }
     435                    shapes += sep + "</shape>\n";
    351436                }
    352437            } catch(IllegalArgumentException ignored) {
    353438            }
    354             if(shapes) {
    355                 stream.write "        <bounds min-lat='${df.format(minlat)}' min-lon='${df.format(minlon)}' max-lat='${df.format(maxlat)}' max-lon='${df.format(maxlon)}'>\n"
    356                 stream.write shapes + "        </bounds>\n"
    357             }
    358             stream.write "    </entry>\n"
    359         }
    360         stream.write "</imagery>\n"
    361         stream.close()
    362     }
    363 
    364     void loadJosmEntries() {
    365         def reader = new ImageryReader(josmInputFile)
    366         josmEntries = reader.parse()
    367 
    368         for (def e : josmEntries) {
    369             if(!getUrl(e)) {
    370               myprintln "+++ JOSM-Entry without URL: " + getDescription(e)
     439            if (!shapes.isEmpty()) {
     440                stream.write("        <bounds min-lat='"+df.format(minlat)+"' min-lon='"+df.format(minlon)+"' max-lat='"+df.format(maxlat)+"' max-lon='"+df.format(maxlon)+"'>\n");
     441                stream.write(shapes + "        </bounds>\n");
     442            }
     443            stream.write("    </entry>\n");
     444        }
     445        stream.write("</imagery>\n");
     446        stream.close();
     447    }
     448
     449    void loadJosmEntries() throws IOException, SAXException, ReflectiveOperationException {
     450        try (ImageryReader reader = new ImageryReader(josmInputFile)) {
     451            josmEntries = reader.parse();
     452        }
     453
     454        for (ImageryInfo e : josmEntries) {
     455            if (isBlank(getUrl(e))) {
     456                myprintln("+++ JOSM-Entry without URL: " + getDescription(e));
     457                continue;
     458            }
     459            if (isBlank(getName(e))) {
     460                myprintln("+++ JOSM-Entry without Name: " + getDescription(e));
     461                continue;
     462            }
     463            String url = getUrlStripped(e);
     464            if (url.contains("{z}")) {
     465                myprintln("+++ JOSM-URL uses {z} instead of {zoom}: "+url);
     466                url = url.replace("{z}","{zoom}");
     467            }
     468            if (josmUrls.containsKey(url)) {
     469                myprintln("+++ JOSM-URL is not unique: "+url);
     470            } else {
     471                josmUrls.put(url, e);
     472            }
     473            for (ImageryInfo m : e.getMirrors()) {
     474                url = getUrlStripped(m);
     475                Field origNameField = ImageryInfo.class.getDeclaredField("origName");
     476                ReflectionUtils.setObjectsAccessible(origNameField);
     477                origNameField.set(m, m.getOriginalName().replaceAll(" mirror server( \\d+)?",""));
     478                if (josmUrls.containsKey(url)) {
     479                    myprintln("+++ JOSM-Mirror-URL is not unique: "+url);
     480                } else {
     481                    josmUrls.put(url, m);
     482                    josmMirrors.put(url, m);
     483                }
     484            }
     485        }
     486        myprintln("*** Loaded "+josmEntries.size()+" entries (JOSM). ***");
     487    }
     488
     489    void checkInOneButNotTheOther() throws IOException {
     490        List<String> le = new LinkedList<>(eliUrls.keySet());
     491        List<String> lj = new LinkedList<>(josmUrls.keySet());
     492
     493        List<String> ke = new LinkedList<>(le);
     494        for (String url : ke) {
     495            if (lj.contains(url)) {
     496                le.remove(url);
     497                lj.remove(url);
     498            }
     499        }
     500
     501        if (!le.isEmpty() && !lj.isEmpty()) {
     502            ke = new LinkedList<>(le);
     503            for (String urle : ke) {
     504                JsonObject e = eliUrls.get(urle);
     505                String ide = getId(e);
     506                String urlhttps = urle.replace("http:","https:");
     507                if (lj.contains(urlhttps)) {
     508                    myprintln("+ Missing https: "+getDescription(e));
     509                    eliUrls.put(urlhttps, eliUrls.get(urle));
     510                    eliUrls.remove(urle);
     511                    le.remove(urle);
     512                    lj.remove(urlhttps);
     513                } else if (isNotBlank(ide)) {
     514                    List<String> kj = new LinkedList<>(lj);
     515                    for (String urlj : kj) {
     516                        ImageryInfo j = josmUrls.get(urlj);
     517                        String idj = getId(j);
     518
     519                        if (ide.equals(idj) && Objects.equals(getType(j), getType(e))) {
     520                            myprintln("* URL for id "+idj+" differs ("+urle+"): "+getDescription(j));
     521                            le.remove(urle);
     522                            lj.remove(urlj);
     523                            // replace key for this entry with JOSM URL
     524                            eliUrls.remove(urle);
     525                            eliUrls.put(urlj,e);
     526                            break;
     527                        }
     528                    }
     529                }
     530            }
     531        }
     532
     533        myprintln("*** URLs found in ELI but not in JOSM ("+le.size()+"): ***");
     534        Collections.sort(le);
     535        if (!le.isEmpty()) {
     536            for (String l : le) {
     537                myprintln("-  " + getDescription(eliUrls.get(l)));
     538            }
     539        }
     540        myprintln("*** URLs found in JOSM but not in ELI ("+lj.size()+"): ***");
     541        Collections.sort(lj);
     542        if (!lj.isEmpty()) {
     543            for (String l : lj) {
     544                myprintln("+  " + getDescription(josmUrls.get(l)));
     545            }
     546        }
     547    }
     548
     549    void checkCommonEntries() throws IOException {
     550        myprintln("*** Same URL, but different name: ***");
     551        for (String url : eliUrls.keySet()) {
     552            JsonObject e = eliUrls.get(url);
     553            if (!josmUrls.containsKey(url)) continue;
     554            ImageryInfo j = josmUrls.get(url);
     555            String ename = getName(e).replace("'","\u2019");
     556            String jname = getName(j).replace("'","\u2019");
     557            if (!ename.equals(jname)) {
     558                myprintln("* Name differs ('"+getName(e)+"' != '"+getName(j)+"'): "+getUrl(j));
     559            }
     560        }
     561
     562        myprintln("*** Same URL, but different Id: ***");
     563        for (String url : eliUrls.keySet()) {
     564            JsonObject e = eliUrls.get(url);
     565            if (!josmUrls.containsKey(url)) continue;
     566            ImageryInfo j = josmUrls.get(url);
     567            String ename = getId(e);
     568            String jname = getId(j);
     569            if (!Objects.equals(ename, jname)) {
     570                myprintln("# Id differs ('"+getId(e)+"' != '"+getId(j)+"'): "+getUrl(j));
     571            }
     572        }
     573
     574        myprintln("*** Same URL, but different type: ***");
     575        for (String url : eliUrls.keySet()) {
     576            JsonObject e = eliUrls.get(url);
     577            if (!josmUrls.containsKey(url)) continue;
     578            ImageryInfo j = josmUrls.get(url);
     579            if (!Objects.equals(getType(e), getType(j))) {
     580                myprintln("* Type differs ("+getType(e)+" != "+getType(j)+"): "+getName(j)+" - "+getUrl(j));
     581            }
     582        }
     583
     584        myprintln("*** Same URL, but different zoom bounds: ***");
     585        for (String url : eliUrls.keySet()) {
     586            JsonObject e = eliUrls.get(url);
     587            if (!josmUrls.containsKey(url)) continue;
     588            ImageryInfo j = josmUrls.get(url);
     589
     590            Integer eMinZoom = getMinZoom(e);
     591            Integer jMinZoom = getMinZoom(j);
     592            /* dont warn for entries copied from the base of the mirror */
     593            if (eMinZoom == null && "wms".equals(getType(j)) && j.getName().contains(" mirror"))
     594                jMinZoom = null;
     595            if (!Objects.equals(eMinZoom, jMinZoom) && !(Objects.equals(eMinZoom, 0) && jMinZoom == null)) {
     596                myprintln("* Minzoom differs ("+eMinZoom+" != "+jMinZoom+"): "+getDescription(j));
     597            }
     598            Integer eMaxZoom = getMaxZoom(e);
     599            Integer jMaxZoom = getMaxZoom(j);
     600            /* dont warn for entries copied from the base of the mirror */
     601            if (eMaxZoom == null && "wms".equals(getType(j)) && j.getName().contains(" mirror"))
     602                jMaxZoom = null;
     603            if (!Objects.equals(eMaxZoom, jMaxZoom)) {
     604                myprintln("* Maxzoom differs ("+eMaxZoom+" != "+jMaxZoom+"): "+getDescription(j));
     605            }
     606        }
     607
     608        myprintln("*** Same URL, but different country code: ***");
     609        for (String url : eliUrls.keySet()) {
     610            JsonObject e = eliUrls.get(url);
     611            if (!josmUrls.containsKey(url)) continue;
     612            ImageryInfo j = josmUrls.get(url);
     613            String cce = getCountryCode(e);
     614            if ("ZZ".equals(cce)) { /* special ELI country code */
     615                cce = null;
     616            }
     617            if (cce != null && !cce.equals(getCountryCode(j))) {
     618                myprintln("* Country code differs ("+getCountryCode(e)+" != "+getCountryCode(j)+"): "+getDescription(j));
     619            }
     620        }
     621        myprintln("*** Same URL, but different quality: ***");
     622        for (String url : eliUrls.keySet()) {
     623            JsonObject e = eliUrls.get(url);
     624            if (!josmUrls.containsKey(url)) {
     625              String q = getQuality(e);
     626              if ("eli-best".equals(q)) {
     627                  myprintln("- Quality best entry not in JOSM for "+getDescription(e));
     628              }
    371629              continue;
    372630            }
    373             if(!getName(e)) {
    374               myprintln "+++ JOSM-Entry without Name: " + getDescription(e)
    375               continue;
    376             }
    377             def url = getUrlStripped(e)
    378             if (url.contains("{z}")) {
    379                 myprintln "+++ JOSM-URL uses {z} instead of {zoom}: "+url
    380                 url = url.replace("{z}","{zoom}")
    381             }
    382             if (josmUrls.containsKey(url)) {
    383                 myprintln "+++ JOSM-URL is not unique: "+url
    384             } else {
    385                 josmUrls.put(url, e)
    386             }
    387             for (def m : e.getMirrors()) {
    388                 url = getUrlStripped(m)
    389                 m.origName = m.getOriginalName().replaceAll(" mirror server( \\d+)?","")
    390                 if (josmUrls.containsKey(url)) {
    391                     myprintln "+++ JOSM-Mirror-URL is not unique: "+url
    392                 } else {
    393                     josmUrls.put(url, m)
    394                     josmMirrors.put(url, m)
    395                 }
    396             }
    397         }
    398         myprintln "*** Loaded ${josmEntries.size()} entries (JOSM). ***"
    399     }
    400 
    401     void checkInOneButNotTheOther() {
    402         def le = new LinkedList<String>(eliUrls.keySet())
    403         def lj = new LinkedList<String>(josmUrls.keySet())
    404 
    405         def ke = new LinkedList<String>(le)
    406         for (def url : ke) {
    407             if(lj.contains(url)) {
    408                 le.remove(url)
    409                 lj.remove(url)
    410             }
    411         }
    412 
    413         if(le && lj) {
    414             ke = new LinkedList<String>(le)
    415             for (def urle : ke) {
    416                 def e = eliUrls.get(urle)
    417                 def ide = getId(e)
    418                 String urlhttps = urle.replace("http:","https:")
    419                 if(lj.contains(urlhttps))
    420                 {
    421                     myprintln "+ Missing https: ${getDescription(e)}"
    422                     eliUrls.put(urlhttps, eliUrls.get(urle))
    423                     eliUrls.remove(urle)
    424                     le.remove(urle)
    425                     lj.remove(urlhttps)
    426                 } else if(ide) {
    427                     def kj = new LinkedList<String>(lj)
    428                     for (def urlj : kj) {
    429                         def j = josmUrls.get(urlj)
    430                         def idj = getId(j)
    431 
    432                         if (ide.equals(idj) && getType(j) == getType(e)) {
    433                             myprintln "* URL for id ${idj} differs ($urle): ${getDescription(j)}"
    434                             le.remove(urle)
    435                             lj.remove(urlj)
    436                             /* replace key for this entry with JOSM URL */
    437                             eliUrls.remove(urle)
    438                             eliUrls.put(urlj,e)
    439                             break
    440                         }
    441                     }
    442                 }
    443             }
    444         }
    445 
    446         myprintln "*** URLs found in ELI but not in JOSM (${le.size()}): ***"
    447         le.sort()
    448         if (!le.isEmpty()) {
    449             for (def l : le) {
    450                 myprintln "-  " + getDescription(eliUrls.get(l))
    451             }
    452         }
    453         myprintln "*** URLs found in JOSM but not in ELI (${lj.size()}): ***"
    454         lj.sort()
    455         if (!lj.isEmpty()) {
    456             for (def l : lj) {
    457                 myprintln "+  " + getDescription(josmUrls.get(l))
    458             }
    459         }
    460     }
    461 
    462     void checkCommonEntries() {
    463         myprintln "*** Same URL, but different name: ***"
    464         for (def url : eliUrls.keySet()) {
    465             def e = eliUrls.get(url)
    466             if (!josmUrls.containsKey(url)) continue
    467             def j = josmUrls.get(url)
    468             def ename = getName(e).replace("'","\u2019")
    469             def jname = getName(j).replace("'","\u2019")
    470             if (!ename.equals(jname)) {
    471                 myprintln "* Name differs ('${getName(e)}' != '${getName(j)}'): ${getUrl(j)}"
    472             }
    473         }
    474 
    475         myprintln "*** Same URL, but different Id: ***"
    476         for (def url : eliUrls.keySet()) {
    477             def e = eliUrls.get(url)
    478             if (!josmUrls.containsKey(url)) continue
    479             def j = josmUrls.get(url)
    480             def ename = getId(e)
    481             def jname = getId(j)
    482             if (!ename.equals(jname)) {
    483                 myprintln "# Id differs ('${getId(e)}' != '${getId(j)}'): ${getUrl(j)}"
    484             }
    485         }
    486 
    487         myprintln "*** Same URL, but different type: ***"
    488         for (def url : eliUrls.keySet()) {
    489             def e = eliUrls.get(url)
    490             if (!josmUrls.containsKey(url)) continue
    491             def j = josmUrls.get(url)
    492             if (!getType(e).equals(getType(j))) {
    493                 myprintln "* Type differs (${getType(e)} != ${getType(j)}): ${getName(j)} - ${getUrl(j)}"
    494             }
    495         }
    496 
    497         myprintln "*** Same URL, but different zoom bounds: ***"
    498         for (def url : eliUrls.keySet()) {
    499             def e = eliUrls.get(url)
    500             if (!josmUrls.containsKey(url)) continue
    501             def j = josmUrls.get(url)
    502 
    503             Integer eMinZoom = getMinZoom(e)
    504             Integer jMinZoom = getMinZoom(j)
    505             /* dont warn for entries copied from the base of the mirror */
    506             if(eMinZoom == null && "wms".equals(getType(j)) && j.getName() =~ / mirror/)
    507                 jMinZoom = null;
    508             if (eMinZoom != jMinZoom  && !(eMinZoom == 0 && jMinZoom == null)) {
    509                 myprintln "* Minzoom differs (${eMinZoom} != ${jMinZoom}): ${getDescription(j)}"
    510             }
    511             Integer eMaxZoom = getMaxZoom(e)
    512             Integer jMaxZoom = getMaxZoom(j)
    513             /* dont warn for entries copied from the base of the mirror */
    514             if(eMaxZoom == null && "wms".equals(getType(j)) && j.getName() =~ / mirror/)
    515                 jMaxZoom = null;
    516             if (eMaxZoom != jMaxZoom) {
    517                 myprintln "* Maxzoom differs (${eMaxZoom} != ${jMaxZoom}): ${getDescription(j)}"
    518             }
    519         }
    520 
    521         myprintln "*** Same URL, but different country code: ***"
    522         for (def url : eliUrls.keySet()) {
    523             def e = eliUrls.get(url)
    524             if (!josmUrls.containsKey(url)) continue
    525             def j = josmUrls.get(url)
    526             def cce = getCountryCode(e)
    527             if ("ZZ".equals(cce)) { /* special ELI country code */
    528                 cce = null
    529             }
    530             if (!cce.equals(getCountryCode(j))) {
    531                 myprintln "* Country code differs (${getCountryCode(e)} != ${getCountryCode(j)}): ${getDescription(j)}"
    532             }
    533         }
    534         myprintln "*** Same URL, but different quality: ***"
    535         for (def url : eliUrls.keySet()) {
    536             def e = eliUrls.get(url)
    537             if (!josmUrls.containsKey(url)) {
    538               def q = getQuality(e)
    539               if("eli-best".equals(q)) {
    540                 myprintln "- Quality best entry not in JOSM for ${getDescription(e)}"
    541               }
    542               continue
    543             }
    544             def j = josmUrls.get(url)
    545             if (!getQuality(e).equals(getQuality(j))) {
    546                 myprintln "* Quality differs (${getQuality(e)} != ${getQuality(j)}): ${getDescription(j)}"
    547             }
    548         }
    549         myprintln "*** Same URL, but different dates: ***"
    550         for (def url : eliUrls.keySet()) {
    551             def ed = getDate(eliUrls.get(url))
    552             if (!josmUrls.containsKey(url)) continue
    553             def j = josmUrls.get(url)
    554             def jd = getDate(j)
     631            ImageryInfo j = josmUrls.get(url);
     632            if (!Objects.equals(getQuality(e), getQuality(j))) {
     633                myprintln("* Quality differs ("+getQuality(e)+" != "+getQuality(j)+"): "+getDescription(j));
     634            }
     635        }
     636        myprintln("*** Same URL, but different dates: ***");
     637        Pattern pattern = Pattern.compile("^(.*;)(\\d\\d\\d\\d)(-(\\d\\d)(-(\\d\\d))?)?$");
     638        for (String url : eliUrls.keySet()) {
     639            String ed = getDate(eliUrls.get(url));
     640            if (!josmUrls.containsKey(url)) continue;
     641            ImageryInfo j = josmUrls.get(url);
     642            String jd = getDate(j);
    555643            // The forms 2015;- or -;2015 or 2015;2015 are handled equal to 2015
    556             String ef = ed.replaceAll("\\A-;","").replaceAll(";-\\z","").replaceAll("\\A([0-9-]+);\\1\\z","\$1")
     644            String ef = ed.replaceAll("\\A-;","").replaceAll(";-\\z","").replaceAll("\\A([0-9-]+);\\1\\z", "$1");
    557645            // ELI has a strange and inconsistent used end_date definition, so we try again with subtraction by one
    558             String ed2 = ed
    559             def reg = (ed =~ /^(.*;)(\d\d\d\d)(-(\d\d)(-(\d\d))?)?$/)
    560             if(reg != null && reg.count == 1) {
    561                 Calendar cal = Calendar.getInstance()
    562                 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)
    563                 cal.add(Calendar.DAY_OF_MONTH, -1)
    564                 ed2 = reg[0][1] + cal.get(Calendar.YEAR)
    565                 if (reg[0][4] != null)
    566                     ed2 += "-" + String.format("%02d", cal.get(Calendar.MONTH)+1)
    567                 if (reg[0][6] != null)
    568                     ed2 += "-" + String.format("%02d", cal.get(Calendar.DAY_OF_MONTH))
    569             }
    570             String ef2 = ed2.replaceAll("\\A-;","").replaceAll(";-\\z","").replaceAll("\\A([0-9-]+);\\1\\z","\$1")
     646            String ed2 = ed;
     647            Matcher m = pattern.matcher(ed);
     648            if (m.matches()) {
     649                Calendar cal = Calendar.getInstance();
     650                cal.set(Integer.valueOf(m.group(2)),
     651                        m.group(4) == null ? 0 : Integer.valueOf(m.group(4))-1,
     652                        m.group(6) == null ? 1 : Integer.valueOf(m.group(6)));
     653                cal.add(Calendar.DAY_OF_MONTH, -1);
     654                ed2 = m.group(1) + cal.get(Calendar.YEAR);
     655                if (m.group(4) != null)
     656                    ed2 += "-" + String.format("%02d", cal.get(Calendar.MONTH)+1);
     657                if (m.group(6) != null)
     658                    ed2 += "-" + String.format("%02d", cal.get(Calendar.DAY_OF_MONTH));
     659            }
     660            String ef2 = ed2.replaceAll("\\A-;","").replaceAll(";-\\z","").replaceAll("\\A([0-9-]+);\\1\\z", "$1");
    571661            if (!ed.equals(jd) && !ef.equals(jd) && !ed2.equals(jd) && !ef2.equals(jd)) {
    572                 String t = "'${ed}'"
     662                String t = "'"+ed+"'";
    573663                if (!ed.equals(ef)) {
    574                     t += " or '${ef}'"
     664                    t += " or '"+ef+"'";
    575665                }
    576666                if (jd.isEmpty()) {
    577                     myprintln "- Missing JOSM date (${t}): ${getDescription(j)}"
     667                    myprintln("- Missing JOSM date ("+t+"): "+getDescription(j));
    578668                } else if (!ed.isEmpty()) {
    579                     myprintln "* Date differs ('${t}' != '${jd}'): ${getDescription(j)}"
    580                 } else if (!options.nomissingeli) {
    581                     myprintln "+ Missing ELI date ('${jd}'): ${getDescription(j)}"
    582                 }
    583             }
    584         }
    585         myprintln "*** Same URL, but different information: ***"
    586         for (def url : eliUrls.keySet()) {
    587             if (!josmUrls.containsKey(url)) continue
    588             def e = eliUrls.get(url)
    589             def j = josmUrls.get(url)
    590 
    591             def et = getDescriptions(e)
    592             def jt = getDescriptions(j)
    593             et = (et.size() > 0) ? et["en"] : ""
    594             jt = (jt.size() > 0) ? jt["en"] : ""
     669                    myprintln("* Date differs ('"+t+"' != '"+jd+"'): "+getDescription(j));
     670                } else if (!optionNoEli) {
     671                    myprintln("+ Missing ELI date ('"+jd+"'): "+getDescription(j));
     672                }
     673            }
     674        }
     675        myprintln("*** Same URL, but different information: ***");
     676        for (String url : eliUrls.keySet()) {
     677            if (!josmUrls.containsKey(url)) continue;
     678            JsonObject e = eliUrls.get(url);
     679            ImageryInfo j = josmUrls.get(url);
     680
     681            String et = getDescriptions(e).getOrDefault("en", "");
     682            String jt = getDescriptions(j).getOrDefault("en", "");
    595683            if (!et.equals(jt)) {
    596                 if (!jt) {
    597                     myprintln "- Missing JOSM description (${et}): ${getDescription(j)}"
    598                 } else if (et) {
    599                     myprintln "* Description differs ('${et}' != '${jt}'): ${getDescription(j)}"
    600                 } else if (!options.nomissingeli) {
    601                     myprintln "+ Missing ELI description ('${jt}'): ${getDescription(j)}"
    602                 }
    603             }
    604 
    605             et = getPermissionReferenceUrl(e)
    606             jt = getPermissionReferenceUrl(j)
    607             def jt2 = getTermsOfUseUrl(j)
    608             if (!jt) jt = jt2
    609             if (!et.equals(jt)) {
    610                 if (!jt) {
    611                     myprintln "- Missing JOSM license URL (${et}): ${getDescription(j)}"
    612                 } else if (et) {
    613                     def ethttps = et.replace("http:","https:")
    614                     if(!jt2 || !(jt2.equals(ethttps) || jt2.equals(et+"/") || jt2.equals(ethttps+"/"))) {
    615                         if(jt.equals(ethttps) || jt.equals(et+"/") || jt.equals(ethttps+"/")) {
    616                             myprintln "+ License URL differs ('${et}' != '${jt}'): ${getDescription(j)}"
     684                if (jt.isEmpty()) {
     685                    myprintln("- Missing JOSM description ("+et+"): "+getDescription(j));
     686                } else if (!et.isEmpty()) {
     687                    myprintln("* Description differs ('"+et+"' != '"+jt+"'): "+getDescription(j));
     688                } else if (!optionNoEli) {
     689                    myprintln("+ Missing ELI description ('"+jt+"'): "+getDescription(j));
     690                }
     691            }
     692
     693            et = getPermissionReferenceUrl(e);
     694            jt = getPermissionReferenceUrl(j);
     695            String jt2 = getTermsOfUseUrl(j);
     696            if (isBlank(jt)) jt = jt2;
     697            if (!Objects.equals(et, jt)) {
     698                if (isBlank(jt)) {
     699                    myprintln("- Missing JOSM license URL ("+et+"): "+getDescription(j));
     700                } else if (isNotBlank(et)) {
     701                    String ethttps = et.replace("http:", "https:");
     702                    if (isBlank(jt2) || !(jt2.equals(ethttps) || jt2.equals(et+"/") || jt2.equals(ethttps+"/"))) {
     703                        if (jt.equals(ethttps) || jt.equals(et+"/") || jt.equals(ethttps+"/")) {
     704                            myprintln("+ License URL differs ('"+et+"' != '"+jt+"'): "+getDescription(j));
    617705                        } else {
    618                             def ja = getAttributionUrl(j)
    619                             if (ja && (ja.equals(et) || ja.equals(ethttps) || ja.equals(et+"/") || ja.equals(ethttps+"/"))) {
    620                                myprintln "+ ELI License URL in JOSM Attribution: ${getDescription(j)}"
     706                            String ja = getAttributionUrl(j);
     707                            if (ja != null && (ja.equals(et) || ja.equals(ethttps) || ja.equals(et+"/") || ja.equals(ethttps+"/"))) {
     708                               myprintln("+ ELI License URL in JOSM Attribution: "+getDescription(j));
    621709                            } else {
    622                                 myprintln "* License URL differs ('${et}' != '${jt}'): ${getDescription(j)}"
     710                                myprintln("* License URL differs ('"+et+"' != '"+jt+"'): "+getDescription(j));
    623711                            }
    624712                        }
    625713                    }
    626                 } else if (!options.nomissingeli) {
    627                     myprintln "+ Missing ELI license URL ('${jt}'): ${getDescription(j)}"
    628                 }
    629             }
    630 
    631             et = getAttributionUrl(e)
    632             jt = getAttributionUrl(j)
    633             if (!et.equals(jt)) {
    634                 if (!jt) {
    635                     myprintln "- Missing JOSM attribution URL (${et}): ${getDescription(j)}"
    636                 } else if (et) {
    637                     def ethttps = et.replace("http:","https:")
    638                     if(jt.equals(ethttps) || jt.equals(et+"/") || jt.equals(ethttps+"/")) {
    639                         myprintln "+ Attribution URL differs ('${et}' != '${jt}'): ${getDescription(j)}"
     714                } else if (!optionNoEli) {
     715                    myprintln("+ Missing ELI license URL ('"+jt+"'): "+getDescription(j));
     716                }
     717            }
     718
     719            et = getAttributionUrl(e);
     720            jt = getAttributionUrl(j);
     721            if (!Objects.equals(et, jt)) {
     722                if (isBlank(jt)) {
     723                    myprintln("- Missing JOSM attribution URL ("+et+"): "+getDescription(j));
     724                } else if (isNotBlank(et)) {
     725                    String ethttps = et.replace("http:","https:");
     726                    if (jt.equals(ethttps) || jt.equals(et+"/") || jt.equals(ethttps+"/")) {
     727                        myprintln("+ Attribution URL differs ('"+et+"' != '"+jt+"'): "+getDescription(j));
    640728                    } else {
    641                         myprintln "* Attribution URL differs ('${et}' != '${jt}'): ${getDescription(j)}"
    642                     }
    643                 } else if (!options.nomissingeli) {
    644                     myprintln "+ Missing ELI attribution URL ('${jt}'): ${getDescription(j)}"
    645                 }
    646             }
    647 
    648             et = getAttributionText(e)
    649             jt = getAttributionText(j)
    650             if (!et.equals(jt)) {
    651                 if (!jt) {
    652                     myprintln "- Missing JOSM attribution text (${et}): ${getDescription(j)}"
    653                 } else if (et) {
    654                     myprintln "* Attribution text differs ('${et}' != '${jt}'): ${getDescription(j)}"
    655                 } else if (!options.nomissingeli) {
    656                     myprintln "+ Missing ELI attribution text ('${jt}'): ${getDescription(j)}"
    657                 }
    658             }
    659 
    660             et = getProjections(e)
    661             jt = getProjections(j)
    662             if (et) { et = new LinkedList(et); Collections.sort(et); et = String.join(" ", et) }
    663             if (jt) { jt = new LinkedList(jt); Collections.sort(jt); jt = String.join(" ", jt) }
    664             if (!et.equals(jt)) {
    665                 if (!jt) {
    666                     def t = getType(e)
    667                     if(t == "wms_endpoint" || t == "tms") {
    668                         myprintln "+ ELI projections for type ${t}: ${getDescription(j)}"
    669                     }
    670                     else {
    671                         myprintln "- Missing JOSM projections (${et}): ${getDescription(j)}"
    672                     }
    673                 } else if (et) {
    674                     if("EPSG:3857 EPSG:4326".equals(et) || "EPSG:3857".equals(et) || "EPSG:4326".equals(et)) {
    675                         myprintln "+ ELI has minimal projections ('${et}' != '${jt}'): ${getDescription(j)}"
     729                        myprintln("* Attribution URL differs ('"+et+"' != '"+jt+"'): "+getDescription(j));
     730                    }
     731                } else if (!optionNoEli) {
     732                    myprintln("+ Missing ELI attribution URL ('"+jt+"'): "+getDescription(j));
     733                }
     734            }
     735
     736            et = getAttributionText(e);
     737            jt = getAttributionText(j);
     738            if (!Objects.equals(et, jt)) {
     739                if (isBlank(jt)) {
     740                    myprintln("- Missing JOSM attribution text ("+et+"): "+getDescription(j));
     741                } else if (isNotBlank(et)) {
     742                    myprintln("* Attribution text differs ('"+et+"' != '"+jt+"'): "+getDescription(j));
     743                } else if (!optionNoEli) {
     744                    myprintln("+ Missing ELI attribution text ('"+jt+"'): "+getDescription(j));
     745                }
     746            }
     747
     748            et = getProjections(e).stream().sorted().collect(Collectors.joining(" "));
     749            jt = getProjections(j).stream().sorted().collect(Collectors.joining(" "));
     750            if (!Objects.equals(et, jt)) {
     751                if (isBlank(jt)) {
     752                    String t = getType(e);
     753                    if ("wms_endpoint".equals(t) || "tms".equals(t)) {
     754                        myprintln("+ ELI projections for type "+t+": "+getDescription(j));
    676755                    } else {
    677                         myprintln "* Projections differ ('${et}' != '${jt}'): ${getDescription(j)}"
    678                     }
    679                 } else if (!options.nomissingeli && !getType(e).equals("tms")) {
    680                     myprintln "+ Missing ELI projections ('${jt}'): ${getDescription(j)}"
    681                 }
    682             }
    683 
    684             et = getDefault(e)
    685             jt = getDefault(j)
    686             if (!et.equals(jt)) {
    687                 if (!jt) {
    688                     myprintln "- Missing JOSM default: ${getDescription(j)}"
    689                 } else if (!options.nomissingeli) {
    690                     myprintln "+ Missing ELI default: ${getDescription(j)}"
    691                 }
    692             }
    693             et = getOverlay(e)
    694             jt = getOverlay(j)
    695             if (!et.equals(jt)) {
    696                 if (!jt) {
    697                     myprintln "- Missing JOSM overlay flag: ${getDescription(j)}"
    698                 } else if (!options.nomissingeli) {
    699                     myprintln "+ Missing ELI overlay flag: ${getDescription(j)}"
    700                 }
    701             }
    702         }
    703         myprintln "*** Mismatching shapes: ***"
    704         for (def url : josmUrls.keySet()) {
    705             def j = josmUrls.get(url)
    706             def num = 1
    707             for (def shape : getShapes(j)) {
    708                 def p = shape.getPoints()
    709                 if(!p[0].equals(p[p.size()-1])) {
    710                     myprintln "+++ JOSM shape $num unclosed: ${getDescription(j)}"
    711                 }
    712                 for (def nump = 1; nump < p.size(); ++nump) {
    713                     if (p[nump-1] == p[nump]) {
    714                         myprintln "+++ JOSM shape $num double point at ${nump-1}: ${getDescription(j)}"
    715                     }
    716                 }
    717                 ++num
    718             }
    719         }
    720         for (def url : eliUrls.keySet()) {
    721             def e = eliUrls.get(url)
    722             def num = 1
    723             def s
     756                        myprintln("- Missing JOSM projections ("+et+"): "+getDescription(j));
     757                    }
     758                } else if (isNotBlank(et)) {
     759                    if ("EPSG:3857 EPSG:4326".equals(et) || "EPSG:3857".equals(et) || "EPSG:4326".equals(et)) {
     760                        myprintln("+ ELI has minimal projections ('"+et+"' != '"+jt+"'): "+getDescription(j));
     761                    } else {
     762                        myprintln("* Projections differ ('"+et+"' != '"+jt+"'): "+getDescription(j));
     763                    }
     764                } else if (!optionNoEli && !"tms".equals(getType(e))) {
     765                    myprintln("+ Missing ELI projections ('"+jt+"'): "+getDescription(j));
     766                }
     767            }
     768
     769            boolean ed = getDefault(e);
     770            boolean jd = getDefault(j);
     771            if (ed != jd) {
     772                if (!jd) {
     773                    myprintln("- Missing JOSM default: "+getDescription(j));
     774                } else if (!optionNoEli) {
     775                    myprintln("+ Missing ELI default: "+getDescription(j));
     776                }
     777            }
     778            boolean eo = getOverlay(e);
     779            boolean jo = getOverlay(j);
     780            if (eo != jo) {
     781                if (!jo) {
     782                    myprintln("- Missing JOSM overlay flag: "+getDescription(j));
     783                } else if (!optionNoEli) {
     784                    myprintln("+ Missing ELI overlay flag: "+getDescription(j));
     785                }
     786            }
     787        }
     788        myprintln("*** Mismatching shapes: ***");
     789        for (String url : josmUrls.keySet()) {
     790            ImageryInfo j = josmUrls.get(url);
     791            int num = 1;
     792            for (Shape shape : getShapes(j)) {
     793                List<Coordinate> p = shape.getPoints();
     794                if(!p.get(0).equals(p.get(p.size()-1))) {
     795                    myprintln("+++ JOSM shape "+num+" unclosed: "+getDescription(j));
     796                }
     797                for (int nump = 1; nump < p.size(); ++nump) {
     798                    if (Objects.equals(p.get(nump-1), p.get(nump))) {
     799                        myprintln("+++ JOSM shape "+num+" double point at "+(nump-1)+": "+getDescription(j));
     800                    }
     801                }
     802                ++num;
     803            }
     804        }
     805        for (String url : eliUrls.keySet()) {
     806            JsonObject e = eliUrls.get(url);
     807            int num = 1;
     808            List<Shape> s = null;
    724809            try {
    725                 s = getShapes(e)
    726                 for (def shape : s) {
    727                     def p = shape.getPoints()
    728                     if(!p[0].equals(p[p.size()-1]) && !options.nomissingeli) {
    729                         myprintln "+++ ELI shape $num unclosed: ${getDescription(e)}"
    730                     }
    731                     for (def nump = 1; nump < p.size(); ++nump) {
    732                         if (p[nump-1] == p[nump]) {
    733                             myprintln "+++ ELI shape $num double point at ${nump-1}: ${getDescription(e)}"
     810                s = getShapes(e);
     811                for (Shape shape : s) {
     812                    List<Coordinate> p = shape.getPoints();
     813                    if(!p.get(0).equals(p.get(p.size()-1)) && !optionNoEli) {
     814                        myprintln("+++ ELI shape "+num+" unclosed: "+getDescription(e));
     815                    }
     816                    for (int nump = 1; nump < p.size(); ++nump) {
     817                        if (Objects.equals(p.get(nump-1), p.get(nump))) {
     818                            myprintln("+++ ELI shape "+num+" double point at "+(nump-1)+": "+getDescription(e));
    734819                        }
    735820                    }
    736                     ++num
    737                 }
    738             } catch(IllegalArgumentException err) {
    739                 def desc = getDescription(e)
    740                 myprintln("* Invalid data in ELI geometry for $desc: ${err.getMessage()}")
     821                    ++num;
     822                }
     823            } catch (IllegalArgumentException err) {
     824                String desc = getDescription(e);
     825                myprintln("* Invalid data in ELI geometry for "+desc+": "+err.getMessage());
    741826            }
    742827            if (s == null || !josmUrls.containsKey(url)) {
    743                 continue
    744             }
    745             def j = josmUrls.get(url)
    746             def js = getShapes(j)
    747             if(!s.size() && js.size()) {
    748                 if(!options.nomissingeli) {
    749                     myprintln "+ No ELI shape: ${getDescription(j)}"
    750                 }
    751             } else if(!js.size() && s.size()) {
     828                continue;
     829            }
     830            ImageryInfo j = josmUrls.get(url);
     831            List<Shape> js = getShapes(j);
     832            if (s.isEmpty() && !js.isEmpty()) {
     833                if (!optionNoEli) {
     834                    myprintln("+ No ELI shape: "+getDescription(j));
     835                }
     836            } else if(js.isEmpty() && !s.isEmpty()) {
    752837                // don't report boundary like 5 point shapes as difference
    753                 if (s.size() != 1 || s[0].getPoints().size() != 5) {
    754                     myprintln "- No JOSM shape: ${getDescription(j)}"
     838                if (s.size() != 1 || s.get(0).getPoints().size() != 5) {
     839                    myprintln("- No JOSM shape: "+getDescription(j));
    755840                }
    756841            } else if(s.size() != js.size()) {
    757                 myprintln "* Different number of shapes (${s.size()} != ${js.size()}): ${getDescription(j)}"
     842                myprintln("* Different number of shapes ("+s.size()+" != "+js.size()+"): "+getDescription(j));
    758843            } else {
    759                 boolean[] edone = new boolean[s.size()]
    760                 boolean[] jdone = new boolean[js.size()]
    761                 for(def enums = 0; enums < s.size(); ++enums) {
    762                     def ep = s[enums].getPoints()
    763                     for(def jnums = 0; jnums < js.size() && !edone[enums]; ++jnums) {
    764                         def jp = js[jnums].getPoints()
    765                         if(ep.size() == jp.size() && !jdone[jnums]) {
     844                boolean[] edone = new boolean[s.size()];
     845                boolean[] jdone = new boolean[js.size()];
     846                for (int enums = 0; enums < s.size(); ++enums) {
     847                    List<Coordinate> ep = s.get(enums).getPoints();
     848                    for (int jnums = 0; jnums < js.size() && !edone[enums]; ++jnums) {
     849                        List<Coordinate> jp = js.get(jnums).getPoints();
     850                        if (ep.size() == jp.size() && !jdone[jnums]) {
    766851                            boolean err = false;
    767                             for(def nump = 0; nump < ep.size() && !err; ++nump) {
    768                                 def ept = ep[nump]
    769                                 def jpt = jp[nump]
     852                            for(int nump = 0; nump < ep.size() && !err; ++nump) {
     853                                Coordinate ept = ep.get(nump);
     854                                Coordinate jpt = jp.get(nump);
    770855                                if(Math.abs(ept.getLat()-jpt.getLat()) > 0.00001 || Math.abs(ept.getLon()-jpt.getLon()) > 0.00001)
    771                                     err = true
     856                                    err = true;
    772857                            }
    773858                            if(!err) {
    774                                 edone[enums] = true
    775                                 jdone[jnums] = true
    776                                 break
     859                                edone[enums] = true;
     860                                jdone[jnums] = true;
     861                                break;
    777862                            }
    778863                        }
    779864                    }
    780865                }
    781                 for(def enums = 0; enums < s.size(); ++enums) {
    782                     def ep = s[enums].getPoints()
    783                     for(def jnums = 0; jnums < js.size() && !edone[enums]; ++jnums) {
    784                         def jp = js[jnums].getPoints()
    785                         if(ep.size() == jp.size() && !jdone[jnums]) {
     866                for (int enums = 0; enums < s.size(); ++enums) {
     867                    List<Coordinate> ep = s.get(enums).getPoints();
     868                    for (int jnums = 0; jnums < js.size() && !edone[enums]; ++jnums) {
     869                        List<Coordinate> jp = js.get(jnums).getPoints();
     870                        if (ep.size() == jp.size() && !jdone[jnums]) {
    786871                            boolean err = false;
    787                             for(def nump = 0; nump < ep.size() && !err; ++nump) {
    788                                 def ept = ep[nump]
    789                                 def jpt = jp[nump]
    790                                 if(Math.abs(ept.getLat()-jpt.getLat()) > 0.00001 || Math.abs(ept.getLon()-jpt.getLon()) > 0.00001) {
    791                                     def numtxt = ((enums == jnums) ? "${enums+1}" : "${enums+1}/${jnums+1}")
    792                                     myprintln "* Different coordinate for point ${nump+1} of shape $numtxt: ${getDescription(j)}"
    793                                     break
     872                            for (int nump = 0; nump < ep.size() && !err; ++nump) {
     873                                Coordinate ept = ep.get(nump);
     874                                Coordinate jpt = jp.get(nump);
     875                                if (Math.abs(ept.getLat()-jpt.getLat()) > 0.00001 || Math.abs(ept.getLon()-jpt.getLon()) > 0.00001) {
     876                                    String numtxt = Integer.toString(enums+1);
     877                                    if (enums != jnums) {
     878                                        numtxt += '/' + Integer.toString(jnums+1);
     879                                    }
     880                                    myprintln("* Different coordinate for point "+(nump+1)+" of shape "+numtxt+": "+getDescription(j));
     881                                    break;
    794882                                }
    795883                            }
    796                             edone[enums] = true
    797                             jdone[jnums] = true
    798                             break
     884                            edone[enums] = true;
     885                            jdone[jnums] = true;
     886                            break;
    799887                        }
    800888                    }
    801889                }
    802                 for(def enums = 0; enums < s.size(); ++enums) {
    803                     def ep = s[enums].getPoints()
    804                     for(def jnums = 0; jnums < js.size() && !edone[enums]; ++jnums) {
    805                         def jp = js[jnums].getPoints()
    806                         if(!jdone[jnums]) {
    807                             def numtxt = ((enums == jnums) ? "${enums+1}" : "${enums+1}/${jnums+1}")
    808                             myprintln "* Different number of points for shape $numtxt (${ep.size()} ! = ${jp.size()})): ${getDescription(j)}"
    809                             edone[enums] = true
    810                             jdone[jnums] = true
    811                             break
     890                for (int enums = 0; enums < s.size(); ++enums) {
     891                    List<Coordinate> ep = s.get(enums).getPoints();
     892                    for (int jnums = 0; jnums < js.size() && !edone[enums]; ++jnums) {
     893                        List<Coordinate> jp = js.get(jnums).getPoints();
     894                        if (!jdone[jnums]) {
     895                            String numtxt = Integer.toString(enums+1);
     896                            if (enums != jnums) {
     897                                numtxt += '/' + Integer.toString(jnums+1);
     898                            }
     899                            myprintln("* Different number of points for shape "+numtxt+" ("+ep.size()+" ! = "+jp.size()+")): "+getDescription(j));
     900                            edone[enums] = true;
     901                            jdone[jnums] = true;
     902                            break;
    812903                        }
    813904                    }
     
    815906            }
    816907        }
    817         myprintln "*** Mismatching icons: ***"
    818         for (def url : eliUrls.keySet()) {
    819             def e = eliUrls.get(url)
     908        myprintln("*** Mismatching icons: ***");
     909        for (String url : eliUrls.keySet()) {
     910            JsonObject e = eliUrls.get(url);
    820911            if (!josmUrls.containsKey(url)) {
    821                 continue
    822             }
    823             def j = josmUrls.get(url)
    824             def ij = getIcon(j)
    825             def ie = getIcon(e)
    826             if(ij != null && ie == null) {
    827                 if(!options.nomissingeli) {
    828                     myprintln "+ No ELI icon: ${getDescription(j)}"
    829                 }
    830             } else if(ij == null && ie != null) {
    831                 myprintln "- No JOSM icon: ${getDescription(j)}"
    832             } else if(!ij.equals(ie) && !(
     912                continue;
     913            }
     914            ImageryInfo j = josmUrls.get(url);
     915            String ij = getIcon(j);
     916            String ie = getIcon(e);
     917            boolean ijok = isNotBlank(ij);
     918            boolean ieok = isNotBlank(ie);
     919            if (ijok && !ieok) {
     920                if (!optionNoEli) {
     921                    myprintln("+ No ELI icon: "+getDescription(j));
     922                }
     923            } else if (!ijok && ieok) {
     924                myprintln("- No JOSM icon: "+getDescription(j));
     925            } else if (ijok && ieok && !Objects.equals(ij, ie) && !(
    833926              (ie.startsWith("https://osmlab.github.io/editor-layer-index/")
    834927              || ie.startsWith("https://raw.githubusercontent.com/osmlab/editor-layer-index/")) &&
    835928              ij.startsWith("data:"))) {
    836                 def iehttps = ie.replace("http:","https:")
    837                 if(ij.equals(iehttps)) {
    838                     myprintln "+ Different icons: ${getDescription(j)}"
     929                String iehttps = ie.replace("http:","https:");
     930                if (ij.equals(iehttps)) {
     931                    myprintln("+ Different icons: "+getDescription(j));
    839932                } else {
    840                     myprintln "* Different icons: ${getDescription(j)}"
    841                 }
    842             }
    843         }
    844         myprintln "*** Miscellaneous checks: ***"
    845         def josmIds = new HashMap<String, ImageryInfo>()
    846         def all = Projections.getAllProjectionCodes()
     933                    myprintln("* Different icons: "+getDescription(j));
     934                }
     935            }
     936        }
     937        myprintln("*** Miscellaneous checks: ***");
     938        Map<String, ImageryInfo> josmIds = new HashMap<>();
     939        Collection<String> all = Projections.getAllProjectionCodes();
    847940        DomainValidator dv = DomainValidator.getInstance();
    848         for (def url : josmUrls.keySet()) {
    849             def j = josmUrls.get(url)
    850             def id = getId(j)
    851             if("wms".equals(getType(j))) {
    852                 if(!getProjections(j)) {
    853                     myprintln "* WMS without projections: ${getDescription(j)}"
     941        for (String url : josmUrls.keySet()) {
     942            ImageryInfo j = josmUrls.get(url);
     943            String id = getId(j);
     944            if ("wms".equals(getType(j))) {
     945                String urlLc = url.toLowerCase(Locale.ENGLISH);
     946                if (getProjections(j).isEmpty()) {
     947                    myprintln("* WMS without projections: "+getDescription(j));
    854948                } else {
    855                     def unsupported = new LinkedList<String>()
    856                     def old = new LinkedList<String>()
    857                     for (def p : getProjectionsUnstripped(j)) {
    858                         if("CRS:84".equals(p)) {
    859                             if(!(url =~ /(?i)version=1\.3/)) {
    860                                 myprintln "* CRS:84 without WMS 1.3: ${getDescription(j)}"
     949                    List<String> unsupported = new LinkedList<>();
     950                    List<String> old = new LinkedList<>();
     951                    for (String p : getProjectionsUnstripped(j)) {
     952                        if ("CRS:84".equals(p)) {
     953                            if (!urlLc.contains("version=1.3")) {
     954                                myprintln("* CRS:84 without WMS 1.3: "+getDescription(j));
    861955                            }
    862                         } else if(oldproj.containsKey(p)) {
    863                             old.add(p)
    864                         } else if(!all.contains(p) && !ignoreproj.contains(p)) {
    865                             unsupported.add(p)
     956                        } else if (oldproj.containsKey(p)) {
     957                            old.add(p);
     958                        } else if (!all.contains(p) && !ignoreproj.contains(p)) {
     959                            unsupported.add(p);
    866960                        }
    867961                    }
    868                     if (unsupported) {
    869                         def s = String.join(", ", unsupported)
    870                         myprintln "* Projections ${s} not supported by JOSM: ${getDescription(j)}"
    871                     }
    872                     for (def o : old) {
    873                         myprintln "* Projection ${o} is an old unsupported code and has been replaced by ${oldproj.get(o)}: ${getDescription(j)}"
    874                     }
    875                 }
    876                 if((url =~ /(?i)version=1\.3/) && !(url =~ /[Cc][Rr][Ss]=\{proj\}/)) {
    877                     myprintln "* WMS 1.3 with strange CRS specification: ${getDescription(j)}"
    878                 }
    879                 if((url =~ /(?i)version=1\.1/) && !(url =~ /[Ss][Rr][Ss]=\{proj\}/)) {
    880                     myprintln "* WMS 1.1 with strange SRS specification: ${getDescription(j)}"
    881                 }
    882             }
    883             def urls = new LinkedList<String>()
    884             if(!"scanex".equals(getType(j))) {
    885               urls += url
    886             }
    887             def jt = getPermissionReferenceUrl(j)
    888             if(jt && !"Public Domain".equals(jt))
    889               urls += jt
    890             jt = getTermsOfUseUrl(j)
    891             if(jt)
    892               urls += jt
    893             jt = getAttributionUrl(j)
    894             if(jt)
    895               urls += jt
    896             jt = getIcon(j)
    897             if(jt && !(jt =~ /^data:image\/png;base64,/))
    898               urls += jt
    899             for(def u : urls) {
    900                 def m = u =~ /^https?:\/\/([^\/]+?)(:\d+)?\//
    901                 if(!m || u =~ /[ \t]+$/)
    902                     myprintln "* Strange URL '${u}': ${getDescription(j)}"
    903                 else {
    904                     def domain = m[0][1].replaceAll("\\{switch:.*\\}","x")
    905                     def port = m[0][2]
    906                     if (!(domain =~ /^\d+\.\d+\.\d+\.\d+$/) && !dv.isValid(domain))
    907                         myprintln "* Strange Domain '${domain}': ${getDescription(j)}"
    908                     else if (port != null && (port == ":80" || port == ":443")) {
    909                         myprintln "* Useless port '${port}': ${getDescription(j)}"
    910                     }
    911                 }
    912             }
    913 
    914             if(josmMirrors.containsKey(url)) {
    915                 continue
    916             }
    917             if(id == null) {
    918                 myprintln "* No JOSM-ID: ${getDescription(j)}"
    919             } else if(josmIds.containsKey(id)) {
    920                 myprintln "* JOSM-ID ${id} not unique: ${getDescription(j)}"
     962                    if (!unsupported.isEmpty()) {
     963                        myprintln("* Projections "+String.join(", ", unsupported)+" not supported by JOSM: "+getDescription(j));
     964                    }
     965                    for (String o : old) {
     966                        myprintln("* Projection "+o+" is an old unsupported code and has been replaced by "+oldproj.get(o)+": "+getDescription(j));
     967                    }
     968                }
     969                if (urlLc.contains("version=1.3") && !urlLc.contains("crs={proj}")) {
     970                    myprintln("* WMS 1.3 with strange CRS specification: "+getDescription(j));
     971                } else if (urlLc.contains("version=1.1") && !urlLc.contains("srs={proj}")) {
     972                    myprintln("* WMS 1.1 with strange SRS specification: "+getDescription(j));
     973                }
     974            }
     975            List<String> urls = new LinkedList<>();
     976            if (!"scanex".equals(getType(j))) {
     977                urls.add(url);
     978            }
     979            String jt = getPermissionReferenceUrl(j);
     980            if (isNotBlank(jt) && !"Public Domain".equalsIgnoreCase(jt))
     981                urls.add(jt);
     982            jt = getTermsOfUseUrl(j);
     983            if (isNotBlank(jt))
     984                urls.add(jt);
     985            jt = getAttributionUrl(j);
     986            if (isNotBlank(jt))
     987                urls.add(jt);
     988            jt = getIcon(j);
     989            if (isNotBlank(jt) && !jt.startsWith("data:image/png;base64,"))
     990                urls.add(jt);
     991            Pattern patternU = Pattern.compile("^https?://([^/]+?)(:\\d+)?/.*");
     992            for (String u : urls) {
     993                Matcher m = patternU.matcher(u);
     994                if (!m.matches() || u.matches(".*[ \t]+$")) {
     995                    myprintln("* Strange URL '"+u+"': "+getDescription(j));
     996                } else {
     997                    String domain = m.group(1).replaceAll("\\{switch:.*\\}","x");
     998                    String port = m.group(2);
     999                    if (!(domain.matches("^\\d+\\.\\d+\\.\\d+\\.\\d+$")) && !dv.isValid(domain))
     1000                        myprintln("* Strange Domain '"+domain+"': "+getDescription(j));
     1001                    else if (":80".equals(port) || ":443".equals(port)) {
     1002                        myprintln("* Useless port '"+port+"': "+getDescription(j));
     1003                    }
     1004                }
     1005            }
     1006
     1007            if (josmMirrors.containsKey(url)) {
     1008                continue;
     1009            }
     1010            if (isBlank(id)) {
     1011                myprintln("* No JOSM-ID: "+getDescription(j));
     1012            } else if (josmIds.containsKey(id)) {
     1013                myprintln("* JOSM-ID "+id+" not unique: "+getDescription(j));
    9211014            } else {
    922                 josmIds.put(id, j)
    923             }
    924             def d = getDate(j)
    925             if(!d.isEmpty()) {
    926                 def reg = (d =~ /^(-|(\d\d\d\d)(-(\d\d)(-(\d\d))?)?)(;(-|(\d\d\d\d)(-(\d\d)(-(\d\d))?)?))?$/)
    927                 if(reg == null || reg.count != 1) {
    928                     myprintln "* JOSM-Date '${d}' is strange: ${getDescription(j)}"
     1015                josmIds.put(id, j);
     1016            }
     1017            String d = getDate(j);
     1018            if (isNotBlank(d)) {
     1019                Pattern patternD = Pattern.compile("^(-|(\\d\\d\\d\\d)(-(\\d\\d)(-(\\d\\d))?)?)(;(-|(\\d\\d\\d\\d)(-(\\d\\d)(-(\\d\\d))?)?))?$");
     1020                Matcher m = patternD.matcher(d);
     1021                if (!m.matches()) {
     1022                    myprintln("* JOSM-Date '"+d+"' is strange: "+getDescription(j));
    9291023                } else {
    9301024                    try {
    931                         def first = verifyDate(reg[0][2],reg[0][4],reg[0][6])
    932                         def second = verifyDate(reg[0][9],reg[0][11],reg[0][13])
    933                         if(second.compareTo(first) < 0) {
    934                             myprintln "* JOSM-Date '${d}' is strange (second earlier than first): ${getDescription(j)}"
     1025                        Date first = verifyDate(m.group(2), m.group(4), m.group(6));
     1026                        Date second = verifyDate(m.group(9), m.group(11), m.group(13));
     1027                        if (second.compareTo(first) < 0) {
     1028                            myprintln("* JOSM-Date '"+d+"' is strange (second earlier than first): "+getDescription(j));
    9351029                        }
    9361030                    }
    9371031                    catch (Exception e) {
    938                         myprintln "* JOSM-Date '${d}' is strange (${e.getMessage()}): ${getDescription(j)}"
    939                     }
    940                 }
    941             }
    942             if(getAttributionUrl(j) && !getAttributionText(j)) {
    943                 myprintln "* Attribution link without text: ${getDescription(j)}"
    944             }
    945             if(getLogoUrl(j) && !getLogoImage(j)) {
    946                 myprintln "* Logo link without image: ${getDescription(j)}"
    947             }
    948             if(getTermsOfUseText(j) && !getTermsOfUseUrl(j)) {
    949                 myprintln "* Terms of Use text without link: ${getDescription(j)}"
    950             }
    951             def js = getShapes(j)
    952             if(js.size()) {
    953                 def minlat = 1000
    954                 def minlon = 1000
    955                 def maxlat = -1000
    956                 def maxlon = -1000
    957                 for(def s: js) {
    958                     for(def p: s.getPoints()) {
    959                         def lat = p.getLat()
    960                         def lon = p.getLon()
    961                         if(lat > maxlat) maxlat = lat
    962                         if(lon > maxlon) maxlon = lon
    963                         if(lat < minlat) minlat = lat
    964                         if(lon < minlon) minlon = lon
    965                     }
    966                 }
    967                 def b = j.getBounds()
    968                 if(b.getMinLat() != minlat || b.getMinLon() != minlon || b.getMaxLat() != maxlat || b.getMaxLon() != maxlon) {
    969                     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)}"
    970                 }
    971             }
    972             def cat = getCategory(j)
    973             if(cat == null) {
    974                 myprintln "* No category: ${getDescription(j)}"
    975             } else if(cat != "photo" && cat != "map" && cat != "historicmap" && cat != "osmbasedmap" && cat != "historicphoto" && cat != "other") {
    976                 myprintln "* Strange category ${cat}: ${getDescription(j)}"
    977             }
    978         }
    979     }
    980 
    981     /**
     1032                        myprintln("* JOSM-Date '"+d+"' is strange ("+e.getMessage()+"): "+getDescription(j));
     1033                    }
     1034                }
     1035            }
     1036            if (isNotBlank(getAttributionUrl(j)) && isBlank(getAttributionText(j))) {
     1037                myprintln("* Attribution link without text: "+getDescription(j));
     1038            }
     1039            if (isNotBlank(getLogoUrl(j)) && isBlank(getLogoImage(j))) {
     1040                myprintln("* Logo link without image: "+getDescription(j));
     1041            }
     1042            if (isNotBlank(getTermsOfUseText(j)) && isBlank(getTermsOfUseUrl(j))) {
     1043                myprintln("* Terms of Use text without link: "+getDescription(j));
     1044            }
     1045            List<Shape> js = getShapes(j);
     1046            if (!js.isEmpty()) {
     1047                double minlat = 1000;
     1048                double minlon = 1000;
     1049                double maxlat = -1000;
     1050                double maxlon = -1000;
     1051                for (Shape s: js) {
     1052                    for (Coordinate p: s.getPoints()) {
     1053                        double lat = p.getLat();
     1054                        double lon = p.getLon();
     1055                        if(lat > maxlat) maxlat = lat;
     1056                        if(lon > maxlon) maxlon = lon;
     1057                        if(lat < minlat) minlat = lat;
     1058                        if(lon < minlon) minlon = lon;
     1059                    }
     1060                }
     1061                ImageryBounds b = j.getBounds();
     1062                if (b.getMinLat() != minlat || b.getMinLon() != minlon || b.getMaxLat() != maxlat || b.getMaxLon() != maxlon) {
     1063                    myprintln("* Bounds do not match shape (is "+b.getMinLat()+","+b.getMinLon()+","+b.getMaxLat()+","+b.getMaxLon()
     1064                        + ", calculated <bounds min-lat='"+minlat+"' min-lon='"+minlon+"' max-lat='"+maxlat+"' max-lon='"+maxlon+"'>): "+getDescription(j));
     1065                }
     1066            }
     1067            List<String> knownCategories = Arrays.asList("photo", "map", "historicmap", "osmbasedmap", "historicphoto", "other");
     1068            String cat = getCategory(j);
     1069            if (isBlank(cat)) {
     1070                myprintln("* No category: "+getDescription(j));
     1071            } else if (!knownCategories.contains(cat)) {
     1072                myprintln("* Strange category "+cat+": "+getDescription(j));
     1073            }
     1074        }
     1075    }
     1076
     1077    /*
    9821078     * Utility functions that allow uniform access for both ImageryInfo and JsonObject.
    9831079     */
     1080
    9841081    static String getUrl(Object e) {
    985         if (e instanceof ImageryInfo) return e.url
    986         return e.get("properties").getString("url")
    987     }
     1082        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getUrl();
     1083        return ((Map<String, JsonObject>) e).get("properties").getString("url");
     1084    }
     1085
    9881086    static String getUrlStripped(Object e) {
    989         return getUrl(e).replaceAll("\\?(apikey|access_token)=.*","")
    990     }
     1087        return getUrl(e).replaceAll("\\?(apikey|access_token)=.*","");
     1088    }
     1089
    9911090    static String getDate(Object e) {
    992         if (e instanceof ImageryInfo) return e.date ? e.date : ""
    993         def p = e.get("properties")
    994         def start = p.containsKey("start_date") ? p.getString("start_date") : ""
    995         def end = p.containsKey("end_date") ? p.getString("end_date") : ""
     1091        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getDate() != null ? ((ImageryInfo) e).getDate() : "";
     1092        JsonObject p = ((Map<String, JsonObject>) e).get("properties");
     1093        String start = p.containsKey("start_date") ? p.getString("start_date") : "";
     1094        String end = p.containsKey("end_date") ? p.getString("end_date") : "";
    9961095        if(!start.isEmpty() && !end.isEmpty())
    997             return start+";"+end
     1096            return start+";"+end;
    9981097        else if(!start.isEmpty())
    999             return start+";-"
     1098            return start+";-";
    10001099        else if(!end.isEmpty())
    1001             return "-;"+end
    1002         return ""
    1003     }
    1004     static Date verifyDate(String year, String month, String day) {
    1005         def date
     1100            return "-;"+end;
     1101        return "";
     1102    }
     1103
     1104    static Date verifyDate(String year, String month, String day) throws ParseException {
     1105        String date;
    10061106        if(year == null) {
    1007             date = "3000-01-01"
     1107            date = "3000-01-01";
    10081108        } else {
    1009             date = year + "-" + (month == null ? "01" : month) + "-" + (day == null ? "01" : day)
    1010         }
    1011         def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
    1012         df.setLenient(false)
    1013         return df.parse(date)
    1014     }
     1109            date = year + "-" + (month == null ? "01" : month) + "-" + (day == null ? "01" : day);
     1110        }
     1111        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
     1112        df.setLenient(false);
     1113        return df.parse(date);
     1114    }
     1115
    10151116    static String getId(Object e) {
    1016         if (e instanceof ImageryInfo) return e.getId()
    1017         return e.get("properties").getString("id")
    1018     }
     1117        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getId();
     1118        return ((Map<String, JsonObject>) e).get("properties").getString("id");
     1119    }
     1120
    10191121    static String getName(Object e) {
    1020         if (e instanceof ImageryInfo) return e.getOriginalName()
    1021         return e.get("properties").getString("name")
    1022     }
    1023     static List<Object> getMirrors(Object e) {
    1024         if (e instanceof ImageryInfo) return e.getMirrors()
    1025         return []
    1026     }
    1027     static List<Object> getProjections(Object e) {
    1028         def r = []
    1029         def u = getProjectionsUnstripped(e)
    1030         if(u) {
    1031             for (def p : u) {
    1032                 if(!oldproj.containsKey(p) && !("CRS:84".equals(p) && !(getUrlStripped(e) =~ /(?i)version=1\.3/))) {
    1033                     r += p
    1034                 }
    1035             }
    1036         }
    1037         return r
    1038     }
    1039     static List<Object> getProjectionsUnstripped(Object e) {
    1040         def r
     1122        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getOriginalName();
     1123        return ((Map<String, JsonObject>) e).get("properties").getString("name");
     1124    }
     1125
     1126    static List<ImageryInfo> getMirrors(Object e) {
     1127        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getMirrors();
     1128        return Collections.emptyList();
     1129    }
     1130
     1131    static List<String> getProjections(Object e) {
     1132        List<String> r = new ArrayList<>();
     1133        List<String> u = getProjectionsUnstripped(e);
     1134        if (u != null) {
     1135            for (String p : u) {
     1136                if (!oldproj.containsKey(p) && !("CRS:84".equals(p) && !(getUrlStripped(e).matches("(?i)version=1\\.3")))) {
     1137                    r.add(p);
     1138                }
     1139            }
     1140        }
     1141        return r;
     1142    }
     1143
     1144    static List<String> getProjectionsUnstripped(Object e) {
     1145        List<String> r = null;
    10411146        if (e instanceof ImageryInfo) {
    1042             r = e.getServerProjections()
     1147            r = ((ImageryInfo) e).getServerProjections();
    10431148        } else {
    1044             def s = e.get("properties").get("available_projections")
    1045             if (s) {
    1046                 r = []
    1047                 for (def p : s) {
    1048                     r += p.getString()
    1049                 }
    1050             }
    1051         }
    1052         return r ? r : []
    1053     }
     1149            JsonValue s = ((Map<String, JsonObject>) e).get("properties").get("available_projections");
     1150            if (s != null) {
     1151                r = new ArrayList<>();
     1152                for (JsonValue p : s.asJsonArray()) {
     1153                    r.add(((JsonString) p).getString());
     1154                }
     1155            }
     1156        }
     1157        return r != null ? r : Collections.emptyList();
     1158    }
     1159
    10541160    static List<Shape> getShapes(Object e) {
    10551161        if (e instanceof ImageryInfo) {
    1056             def bounds = e.getBounds()
     1162            ImageryBounds bounds = ((ImageryInfo) e).getBounds();
    10571163            if(bounds != null) {
    1058                 return bounds.getShapes()
    1059             }
    1060             return []
    1061         }
    1062         def ex = e.get("geometry")
    1063         if (ex != null && !JsonValue.NULL.equals(ex) && !ex.isNull("coordinates")) {
    1064             def poly = ex.get("coordinates")
    1065             List<Shape> l = []
    1066             for(def shapes: poly) {
    1067                 def s = new Shape()
    1068                 for(def point: shapes) {
    1069                     def lon = point[0].toString()
    1070                     def lat = point[1].toString()
    1071                     s.addPoint(lat, lon)
    1072                 }
    1073                 l.add(s)
    1074             }
    1075             return l
    1076         }
    1077         return []
    1078     }
     1164                return bounds.getShapes();
     1165            }
     1166            return Collections.emptyList();
     1167        }
     1168        JsonValue ex = ((Map<String, JsonValue>) e).get("geometry");
     1169        if (ex != null && !JsonValue.NULL.equals(ex) && !ex.asJsonObject().isNull("coordinates")) {
     1170            JsonArray poly = ex.asJsonObject().getJsonArray("coordinates");
     1171            List<Shape> l = new ArrayList<>();
     1172            for (JsonValue shapes: poly) {
     1173                Shape s = new Shape();
     1174                for (JsonValue point: shapes.asJsonArray()) {
     1175                    String lon = point.asJsonArray().getJsonNumber(0).toString();
     1176                    String lat = point.asJsonArray().getJsonNumber(1).toString();
     1177                    s.addPoint(lat, lon);
     1178                }
     1179                l.add(s);
     1180            }
     1181            return l;
     1182        }
     1183        return Collections.emptyList();
     1184    }
     1185
    10791186    static String getType(Object e) {
    1080         if (e instanceof ImageryInfo) return e.getImageryType().getTypeString()
    1081         return e.get("properties").getString("type")
    1082     }
     1187        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getImageryType().getTypeString();
     1188        return ((Map<String, JsonObject>) e).get("properties").getString("type");
     1189    }
     1190
    10831191    static Integer getMinZoom(Object e) {
    10841192        if (e instanceof ImageryInfo) {
    1085             int mz = e.getMinZoom()
    1086             return mz == 0 ? null : mz
     1193            int mz = ((ImageryInfo) e).getMinZoom();
     1194            return mz == 0 ? null : mz;
    10871195        } else {
    1088             def num = e.get("properties").getJsonNumber("min_zoom")
    1089             if (num == null) return null
    1090             return num.intValue()
    1091         }
    1092     }
     1196            JsonNumber num = ((Map<String, JsonObject>) e).get("properties").getJsonNumber("min_zoom");
     1197            if (num == null) return null;
     1198            return num.intValue();
     1199        }
     1200    }
     1201
    10931202    static Integer getMaxZoom(Object e) {
    10941203        if (e instanceof ImageryInfo) {
    1095             int mz = e.getMaxZoom()
    1096             return mz == 0 ? null : mz
     1204            int mz = ((ImageryInfo) e).getMaxZoom();
     1205            return mz == 0 ? null : mz;
    10971206        } else {
    1098             def num = e.get("properties").getJsonNumber("max_zoom")
    1099             if (num == null) return null
    1100             return num.intValue()
    1101         }
    1102     }
     1207            JsonNumber num = ((Map<String, JsonObject>) e).get("properties").getJsonNumber("max_zoom");
     1208            if (num == null) return null;
     1209            return num.intValue();
     1210        }
     1211    }
     1212
    11031213    static String getCountryCode(Object e) {
    1104         if (e instanceof ImageryInfo) return "".equals(e.getCountryCode()) ? null : e.getCountryCode()
    1105         return e.get("properties").getString("country_code", null)
    1106     }
     1214        if (e instanceof ImageryInfo) return "".equals(((ImageryInfo) e).getCountryCode()) ? null : ((ImageryInfo) e).getCountryCode();
     1215        return ((Map<String, JsonObject>) e).get("properties").getString("country_code", null);
     1216    }
     1217
    11071218    static String getQuality(Object e) {
    1108         if (e instanceof ImageryInfo) return e.isBestMarked() ? "eli-best" : null
    1109         return (e.get("properties").containsKey("best")
    1110             && e.get("properties").getBoolean("best")) ? "eli-best" : null
    1111     }
    1112     static Boolean getOverlay(Object e) {
    1113         if (e instanceof ImageryInfo) return e.isOverlay()
    1114         return (e.get("properties").containsKey("overlay")
    1115             && e.get("properties").getBoolean("overlay"))
    1116     }
     1219        if (e instanceof ImageryInfo) return ((ImageryInfo) e).isBestMarked() ? "eli-best" : null;
     1220        return (((Map<String, JsonObject>) e).get("properties").containsKey("best")
     1221            && ((Map<String, JsonObject>) e).get("properties").getBoolean("best")) ? "eli-best" : null;
     1222    }
     1223
     1224    static boolean getOverlay(Object e) {
     1225        if (e instanceof ImageryInfo) return ((ImageryInfo) e).isOverlay();
     1226        return (((Map<String, JsonObject>) e).get("properties").containsKey("overlay")
     1227            && ((Map<String, JsonObject>) e).get("properties").getBoolean("overlay"));
     1228    }
     1229
    11171230    static String getIcon(Object e) {
    1118         if (e instanceof ImageryInfo) return e.getIcon()
    1119         return e.get("properties").getString("icon", null)
    1120     }
     1231        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getIcon();
     1232        return ((Map<String, JsonObject>) e).get("properties").getString("icon", null);
     1233    }
     1234
    11211235    static String getAttributionText(Object e) {
    1122         if (e instanceof ImageryInfo) return e.getAttributionText(0, null, null)
    1123         try {return e.get("properties").get("attribution").getString("text", null)} catch (NullPointerException ex) {return null}
    1124     }
     1236        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getAttributionText(0, null, null);
     1237        try {
     1238            return ((Map<String, JsonObject>) e).get("properties").getJsonObject("attribution").getString("text", null);
     1239        } catch (NullPointerException ex) {
     1240            return null;
     1241        }
     1242    }
     1243
    11251244    static String getAttributionUrl(Object e) {
    1126         if (e instanceof ImageryInfo) return e.getAttributionLinkURL()
    1127         try {return e.get("properties").get("attribution").getString("url", null)} catch (NullPointerException ex) {return null}
    1128     }
     1245        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getAttributionLinkURL();
     1246        try {
     1247            return ((Map<String, JsonObject>) e).get("properties").getJsonObject("attribution").getString("url", null);
     1248        } catch (NullPointerException ex) {
     1249            return null;
     1250        }
     1251    }
     1252
    11291253    static String getTermsOfUseText(Object e) {
    1130         if (e instanceof ImageryInfo) return e.getTermsOfUseText()
    1131         return null
    1132     }
     1254        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getTermsOfUseText();
     1255        return null;
     1256    }
     1257
    11331258    static String getTermsOfUseUrl(Object e) {
    1134         if (e instanceof ImageryInfo) return e.getTermsOfUseURL()
    1135         return null
    1136     }
     1259        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getTermsOfUseURL();
     1260        return null;
     1261    }
     1262
    11371263    static String getCategory(Object e) {
    11381264        if (e instanceof ImageryInfo) {
    1139             return e.getImageryCategoryOriginalString()
    1140         }
    1141         return null
    1142     }
     1265            return ((ImageryInfo) e).getImageryCategoryOriginalString();
     1266        }
     1267        return null;
     1268    }
     1269
    11431270    static String getLogoImage(Object e) {
    1144         if (e instanceof ImageryInfo) return e.getAttributionImageRaw()
    1145         return null
    1146     }
     1271        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getAttributionImageRaw();
     1272        return null;
     1273    }
     1274
    11471275    static String getLogoUrl(Object e) {
    1148         if (e instanceof ImageryInfo) return e.getAttributionImageURL()
    1149         return null
    1150     }
     1276        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getAttributionImageURL();
     1277        return null;
     1278    }
     1279
    11511280    static String getPermissionReferenceUrl(Object e) {
    1152         if (e instanceof ImageryInfo) return e.getPermissionReferenceURL()
    1153         return e.get("properties").getString("license_url", null)
    1154     }
     1281        if (e instanceof ImageryInfo) return ((ImageryInfo) e).getPermissionReferenceURL();
     1282        return ((Map<String, JsonObject>) e).get("properties").getString("license_url", null);
     1283    }
     1284
    11551285    static Map<String,String> getDescriptions(Object e) {
    1156         Map<String,String> res = new HashMap<String, String>()
     1286        Map<String,String> res = new HashMap<String, String>();
    11571287        if (e instanceof ImageryInfo) {
    1158             String a = e.getDescription()
    1159             if (a) res.put("en", a)
     1288            String a = ((ImageryInfo) e).getDescription();
     1289            if (a != null) res.put("en", a);
    11601290        } else {
    1161             String a = e.get("properties").getString("description", null)
    1162             if (a) res.put("en", a.replaceAll("''","'"))
    1163         }
    1164         return res
    1165     }
    1166     static Boolean getValidGeoreference(Object e) {
    1167         if (e instanceof ImageryInfo) return e.isGeoreferenceValid()
    1168         return false
    1169     }
    1170     static Boolean getDefault(Object e) {
    1171         if (e instanceof ImageryInfo) return e.isDefaultEntry()
    1172         return e.get("properties").getBoolean("default", false)
    1173     }
     1291            String a = ((Map<String, JsonObject>) e).get("properties").getString("description", null);
     1292            if (a != null) res.put("en", a.replaceAll("''","'"));
     1293        }
     1294        return res;
     1295    }
     1296
     1297    static boolean getValidGeoreference(Object e) {
     1298        if (e instanceof ImageryInfo) return ((ImageryInfo) e).isGeoreferenceValid();
     1299        return false;
     1300    }
     1301
     1302    static boolean getDefault(Object e) {
     1303        if (e instanceof ImageryInfo) return ((ImageryInfo) e).isDefaultEntry();
     1304        return ((Map<String, JsonObject>) e).get("properties").getBoolean("default", false);
     1305    }
     1306
    11741307    String getDescription(Object o) {
    1175         def url = getUrl(o)
    1176         def cc = getCountryCode(o)
     1308        String url = getUrl(o);
     1309        String cc = getCountryCode(o);
    11771310        if (cc == null) {
    1178             def j = josmUrls.get(url)
    1179             if (j != null) cc = getCountryCode(j)
     1311            ImageryInfo j = josmUrls.get(url);
     1312            if (j != null) cc = getCountryCode(j);
    11801313            if (cc == null) {
    1181                 def e = eliUrls.get(url)
    1182                 if (e != null) cc = getCountryCode(e)
     1314                JsonObject e = eliUrls.get(url);
     1315                if (e != null) cc = getCountryCode(e);
    11831316            }
    11841317        }
    11851318        if (cc == null) {
    1186             cc = ''
     1319            cc = "";
    11871320        } else {
    1188             cc = "[$cc] "
    1189         }
    1190         def d = cc + getName(o) + " - " + getUrl(o)
    1191         if (options.shorten) {
    1192             def MAXLEN = 140
    1193             if (d.length() > MAXLEN) d = d.substring(0, MAXLEN-1) + "..."
    1194         }
    1195         return d
     1321            cc = "["+cc+"] ";
     1322        }
     1323        String d = cc + getName(o) + " - " + getUrl(o);
     1324        if (optionShorten) {
     1325            final int MAXLEN = 140;
     1326            if (d.length() > MAXLEN) d = d.substring(0, MAXLEN-1) + "...";
     1327        }
     1328        return d;
    11961329    }
    11971330}
Note: See TracChangeset for help on using the changeset viewer.