diff --git a/.bach/bin/bach b/.bach/bin/bach index 0d8a279..3a2e346 100755 --- a/.bach/bin/bach +++ b/.bach/bin/bach @@ -1,8 +1,7 @@ #!/usr/bin/env bash if [[ $1 == 'boot' ]]; then - javac --module-path .bach/bin --add-modules com.github.sormuras.bach -d .bach/workspace/.bach .bach/bin/boot.java - jshell --module-path .bach/bin --add-modules com.github.sormuras.bach --class-path .bach/workspace/.bach .bach/bin/boot.jsh + jshell --module-path .bach/bin --add-modules com.github.sormuras.bach exit $? fi @@ -11,7 +10,7 @@ if [[ $1 == 'init' ]]; then echo "Usage: bach init VERSION" exit 1 fi - java .bach/bin/init.java $2 + jshell -R-Dbach-version=$2 https://git.io/bach-init exit $? fi diff --git a/.bach/bin/bach.bat b/.bach/bin/bach.bat index 4bf2788..494fd59 100644 --- a/.bach/bin/bach.bat +++ b/.bach/bin/bach.bat @@ -1,8 +1,7 @@ @ECHO OFF IF "%~1" == "boot" ( - javac --module-path .bach\bin --add-modules com.github.sormuras.bach -d .bach\workspace\.bach .bach\bin\boot.java - jshell --module-path .bach\bin --add-modules com.github.sormuras.bach --class-path .bach\workspace\.bach .bach\bin\boot.jsh + jshell --module-path .bach\bin --add-modules com.github.sormuras.bach EXIT /B %ERRORLEVEL% ) @@ -11,7 +10,7 @@ IF "%~1" == "init" ( ECHO "Usage: bach init VERSION" EXIT /B 1 ) - java .bach\bin\init.java %2 + jshell -R-Dbach-version=%2 https://git.io/bach-init EXIT /B %ERRORLEVEL% ) diff --git a/.bach/bin/boot.java b/.bach/bin/boot.java deleted file mode 100644 index 9f272bc..0000000 --- a/.bach/bin/boot.java +++ /dev/null @@ -1,745 +0,0 @@ -package bin; - -import com.github.sormuras.bach.Bach; -import com.github.sormuras.bach.Command; -import com.github.sormuras.bach.Options; -import com.github.sormuras.bach.ProjectInfo; -import com.github.sormuras.bach.project.ModuleDeclaration; -import com.github.sormuras.bach.project.Property; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.module.ModuleDescriptor; -import java.lang.module.ModuleFinder; -import java.lang.module.ModuleReference; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.Set; -import java.util.StringJoiner; -import java.util.TreeSet; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.spi.ToolProvider; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -@SuppressWarnings("unused") -public class boot { - - public static Bach bach() { - return bach.get(); - } - - public static void beep() { - System.out.print("\007"); // 🔔 - System.out.flush(); - } - - public static void refresh() { - utils.refresh(ProjectInfo.MODULE); - } - - public static void scaffold() throws Exception { - files.createBachInfoModuleDescriptor(); - files.createBachInfoBuilderClass(); - exports.idea(); - } - - public interface exports { - - static void idea() throws Exception { - var idea = bach().folders().root(".idea"); - if (Files.exists(idea)) { - out("IntelliJ's IDEA directory already exits: %s", idea); - return; - } - - var projectName = bach().project().name(); - Files.createDirectories(idea); - ideaMisc(idea); // ".idea/.gitignore.iml", ".idea/misc.xml", ... - ideaProjectModule(idea, projectName); // ".idea/${name}.iml" - var moduleNames = new ArrayList<>(List.of(projectName)); - if (Files.exists(bach().folders().root(".bach/bach.info"))) { - ideaBachInfoModule(idea); - moduleNames.add("bach.info"); - } - for (var mainModule : bach().project().spaces().main().declarations().map().values()) { - ideaModule(idea, mainModule, false); // TODO include in-module test assets - moduleNames.add(mainModule.name()); - } - for (var testModule : bach().project().spaces().test().declarations().map().values()) { - if (moduleNames.contains(testModule.name())) continue; // exclude in-module tests - ideaModule(idea, testModule, true); - moduleNames.add(testModule.name()); - } - ideaModules(idea, moduleNames); - ideaLibraries(idea); - } - - private static void ideaMisc(Path idea) throws Exception { - Files.writeString( - idea.resolve(".gitignore"), - """ - /out/ - /shelf/ - /workspace.xml - """); - - Files.writeString( - idea.resolve("misc.xml"), - """ - - - - - - - """); - } - - private static void ideaModules(Path idea, List files) throws Exception { - var modules = new StringJoiner(System.lineSeparator()); - for (var file : files) { - modules.add( - """ - """ - .replace("{{FILE}}", file) - .strip()); - } - - Files.writeString( - idea.resolve("modules.xml"), - """ - - - - - {{MODULES}} - - - - """ - .replace("{{MODULES}}", modules.toString().strip().indent(6))); - } - - private static void ideaProjectModule(Path idea, String name) throws Exception { - Files.writeString( - idea.resolve(name + ".iml"), - """ - - - - - - - - - - - - """); - } - - private static void ideaBachInfoModule(Path idea) throws Exception { - Files.writeString( - idea.resolve("bach.info.iml"), - """ - - - - - - - - - - - - - """); - } - - private static void ideaModule(Path idea, ModuleDeclaration module, boolean isTestSource) - throws Exception { - var content = new StringJoiner(System.lineSeparator()); - for (var source : module.sources().list()) { - content.add( - """ - """ - .replace("{{PATH}}", source.path().toString().replace('\\', '/')) - .replace("{{IS_TEST_SOURCE}}", Boolean.toString(isTestSource)) - .strip()); - } - Files.writeString( - idea.resolve(module.name() + ".iml"), - """ - - - - - - {{CONTENT}} - - - - - - - """ - .replace("{{ROOT}}", module.root().toString().replace('\\', '/')) - .replace("{{CONTENT}}", content.toString().strip().indent(6))); - } - - private static void ideaLibraries(Path idea) throws Exception { - var libraries = Files.createDirectories(idea.resolve("libraries")); - Files.writeString( - libraries.resolve("bach_bin.xml"), - """ - - - - - - - - - - - - """ - .replace("{{JAR}}", Bach.jar().getFileName().toString())); - - Files.writeString( - libraries.resolve("bach_external_modules.xml"), - """ - - - - - - - - - - - """); - } - } - - public interface files { - - static void createBachInfoModuleDescriptor() throws Exception { - var file = bach().folders().root(".bach/bach.info/module-info.java"); - if (Files.exists(file)) { - out("File already exists: %s", file); - return; - } - out("Create build information module descriptor: %s", file); - var text = - """ - @com.github.sormuras.bach.ProjectInfo ( - name = "{{PROJECT-NAME}}", - version = "{{PROJECT-VERSION}}" - ) - module bach.info { - requires com.github.sormuras.bach; - provides com.github.sormuras.bach.Bach.Provider with bach.info.Builder; - } - """ - .replace("{{PROJECT-NAME}}", bach().project().name()) - .replace("{{PROJECT-VERSION}}", bach().project().version()); - Files.createDirectories(file.getParent()); - Files.writeString(file, text); - } - - static void createBachInfoBuilderClass() throws Exception { - var file = bach().folders().root(".bach/bach.info/bach/info/Builder.java"); - out("Create builder class: %s", file); - var text = - """ - package bach.info; - - import com.github.sormuras.bach.*; - - public class Builder extends Bach { - public static void main(String... args) { - Bach.main(args); - } - - public static Provider provider() { - return Builder::new; - } - - private Builder(Options options) { - super(options); - } - } - """; - Files.createDirectories(file.getParent()); - Files.writeString(file, text); - } - - static void dir() { - dir(""); - } - - static void dir(String folder) { - dir(folder, "*"); - } - - static void dir(String folder, String glob) { - var win = System.getProperty("os.name", "?").toLowerCase(Locale.ROOT).contains("win"); - var directory = Path.of(folder).toAbsolutePath().normalize(); - var paths = new ArrayList(); - try (var stream = Files.newDirectoryStream(directory, glob)) { - for (var path : stream) { - if (win && Files.isHidden(path)) continue; - paths.add(path); - } - } catch (Exception exception) { - out(exception); - } - paths.sort( - (Path p1, Path p2) -> { - var one = Files.isDirectory(p1); - var two = Files.isDirectory(p2); - if (one && !two) return -1; // directory before file - if (!one && two) return 1; // file after directory - return p1.compareTo(p2); // order lexicographically - }); - long files = 0; - long bytes = 0; - for (var path : paths) { - var name = path.getFileName().toString(); - if (Files.isDirectory(path)) out("%-15s %s", "[+]", name); - else - try { - files++; - var size = Files.size(path); - bytes += size; - out("%,15d %s", size, name); - } catch (Exception exception) { - out(exception); - return; - } - } - var all = paths.size(); - if (all == 0) { - out("Directory %s is empty", directory); - return; - } - out(""); - out("%15d path%s in directory %s", all, all == 1 ? "" : "s", directory); - out("%,15d bytes in %d file%s", bytes, files, files == 1 ? "" : "s"); - } - - static void tree() { - tree(""); - } - - static void tree(String folder) { - tree(folder, name -> name.contains("module-info")); - } - - static void tree(String folder, Predicate fileNameFilter) { - var directory = Path.of(folder).toAbsolutePath(); - out("%s", folder.isEmpty() ? directory : folder); - var files = tree(directory, " ", fileNameFilter); - out(""); - out("%d file%s in tree of %s", files, files == 1 ? "" : "s", directory); - } - - private static int tree(Path directory, String indent, Predicate filter) { - var win = System.getProperty("os.name", "?").toLowerCase(Locale.ROOT).contains("win"); - var files = 0; - try (var stream = Files.newDirectoryStream(directory, "*")) { - for (var path : stream) { - if (win && Files.isHidden(path)) continue; - var name = path.getFileName().toString(); - if (Files.isDirectory(path)) { - out(indent + name + "/"); - if (name.equals(".git")) continue; - files += tree(path, indent + " ", filter); - continue; - } - files++; - if (filter.test(name)) out(indent + name); - } - } catch (Exception exception) { - out(exception); - } - return files; - } - } - - public interface modules { - - /** - * Prints a module description of the given module. - * - * @param module the name of the module to describe - */ - static void describe(String module) { - ModuleFinder.compose( - ModuleFinder.of(bach().folders().workspace("modules")), - ModuleFinder.of(bach().folders().externalModules()), - ModuleFinder.ofSystem()) - .find(module) - .ifPresentOrElse( - reference -> out.accept(describe(reference)), - () -> out.accept("No such module found: " + module)); - } - - /** - * Print a sorted list of all modules locatable by the given module finder. - * - * @param finder the module finder to query for modules - */ - static void describe(ModuleFinder finder) { - var all = finder.findAll(); - all.stream() - .map(ModuleReference::descriptor) - .map(ModuleDescriptor::toNameAndVersion) - .sorted() - .forEach(out); - out("%n-> %d module%s", all.size(), all.size() == 1 ? "" : "s"); - } - - // https://github.com/openjdk/jdk/blob/80380d51d279852f4a24ebbd384921106611bc0c/src/java.base/share/classes/sun/launcher/LauncherHelper.java#L1105 - static String describe(ModuleReference mref) { - var md = mref.descriptor(); - var writer = new StringWriter(); - var print = new PrintWriter(writer); - - // one-line summary - print.print(md.toNameAndVersion()); - mref.location().filter(uri -> !isJrt(uri)).ifPresent(uri -> print.format(" %s", uri)); - if (md.isOpen()) print.print(" open"); - if (md.isAutomatic()) print.print(" automatic"); - print.println(); - - // unqualified exports (sorted by package) - md.exports().stream() - .filter(e -> !e.isQualified()) - .sorted(Comparator.comparing(ModuleDescriptor.Exports::source)) - .forEach(e -> print.format("exports %s%n", toString(e.source(), e.modifiers()))); - - // dependences (sorted by name) - md.requires().stream() - .sorted(Comparator.comparing(ModuleDescriptor.Requires::name)) - .forEach(r -> print.format("requires %s%n", toString(r.name(), r.modifiers()))); - - // service use and provides (sorted by name) - md.uses().stream().sorted().forEach(s -> print.format("uses %s%n", s)); - md.provides().stream() - .sorted(Comparator.comparing(ModuleDescriptor.Provides::service)) - .forEach( - ps -> { - var names = String.join("\n", new TreeSet<>(ps.providers())); - print.format("provides %s with%n%s", ps.service(), names.indent(2)); - }); - - // qualified exports (sorted by package) - md.exports().stream() - .filter(ModuleDescriptor.Exports::isQualified) - .sorted(Comparator.comparing(ModuleDescriptor.Exports::source)) - .forEach( - e -> { - var who = String.join("\n", new TreeSet<>(e.targets())); - print.format("qualified exports %s to%n%s", e.source(), who.indent(2)); - }); - - // open packages (sorted by package) - md.opens().stream() - .sorted(Comparator.comparing(ModuleDescriptor.Opens::source)) - .forEach( - opens -> { - if (opens.isQualified()) print.print("qualified "); - print.format("opens %s", toString(opens.source(), opens.modifiers())); - if (opens.isQualified()) { - var who = String.join("\n", new TreeSet<>(opens.targets())); - print.format(" to%n%s", who.indent(2)); - } else print.println(); - }); - - // non-exported/non-open packages (sorted by name) - var concealed = new TreeSet<>(md.packages()); - md.exports().stream().map(ModuleDescriptor.Exports::source).forEach(concealed::remove); - md.opens().stream().map(ModuleDescriptor.Opens::source).forEach(concealed::remove); - concealed.forEach(p -> print.format("contains %s%n", p)); - - return writer.toString().stripTrailing(); - } - - private static String toString(String name, Set modifiers) { - var strings = modifiers.stream().map(e -> e.toString().toLowerCase()); - return Stream.concat(Stream.of(name), strings).collect(Collectors.joining(" ")); - } - - private static boolean isJrt(URI uri) { - return (uri != null && uri.getScheme().equalsIgnoreCase("jrt")); - } - - private static void findRequiresDirectivesMatching(ModuleFinder finder, String regex) { - var descriptors = - finder.findAll().stream() - .map(ModuleReference::descriptor) - .sorted(Comparator.comparing(ModuleDescriptor::name)) - .toList(); - for (var descriptor : descriptors) { - var matched = - descriptor.requires().stream() - .filter(requires -> requires.name().matches(regex)) - .toList(); - if (matched.isEmpty()) continue; - out.accept(descriptor.toNameAndVersion()); - matched.forEach(requires -> out.accept(" requires " + requires)); - } - } - - interface external { - - static void delete(String module) throws Exception { - var jar = bach().computeExternalModuleFile(module); - out("Delete %s", jar); - Files.deleteIfExists(jar); - } - - static void purge() throws Exception { - var externals = bach().folders().externalModules(); - if (!Files.isDirectory(externals)) return; - try (var jars = Files.newDirectoryStream(externals, "*.jar")) { - for (var jar : jars) - try { - out("Delete %s", jar); - Files.deleteIfExists(jar); - } catch (Exception exception) { - out("Delete failed: %s", jar); - } - } - } - - /** Prints a list of all external modules. */ - static void list() { - describe(ModuleFinder.of(bach().folders().externalModules())); - } - - static void load(String module) { - bach().loadExternalModules(module); - var set = bach().computeMissingExternalModules(); - if (set.isEmpty()) return; - out(""); - missing.list(set); - } - - static void findRequires(String regex) { - var finder = ModuleFinder.of(bach().folders().externalModules()); - findRequiresDirectivesMatching(finder, regex); - } - - interface missing { - - static void list() { - list(bach().computeMissingExternalModules()); - } - - private static void list(Set modules) { - var size = modules.size(); - modules.stream().sorted().forEach(out); - out("%n-> %d module%s missing", size, size == 1 ? " is" : "s are"); - } - - static void resolve() { - bach().loadMissingExternalModules(); - } - } - - interface prepared { - static void loadComGithubSormurasModules() { - loadComGithubSormurasModules("0-ea"); - } - - static void loadComGithubSormurasModules(String version) { - var module = "com.github.sormuras.modules"; - var jar = module + "@" + version + ".jar"; - var uri = "https://github.com/sormuras/modules/releases/download/" + version + "/" + jar; - bach().browser().load(uri, bach().computeExternalModuleFile(module)); - } - } - } - - interface system { - - /** Prints a list of all system modules. */ - static void list() { - describe(ModuleFinder.ofSystem()); - } - - static void findRequires(String regex) { - findRequiresDirectivesMatching(ModuleFinder.ofSystem(), regex); - } - } - } - - public interface tools { - - static String describe(ToolProvider provider) { - var name = provider.name(); - var module = provider.getClass().getModule(); - var by = - Optional.ofNullable(module.getDescriptor()) - .map(ModuleDescriptor::toNameAndVersion) - .orElse(module.toString()); - var info = - switch (name) { - case "bach" -> "Build modular Java projects"; - case "google-java-format" -> "Reformat Java sources to comply with Google Java Style"; - case "jar" -> "Create an archive for classes and resources, and update or restore them"; - case "javac" -> "Read Java compilation units (*.java) and compile them into classes"; - case "javadoc" -> "Generate HTML pages of API documentation from Java source files"; - case "javap" -> "Disassemble one or more class files"; - case "jdeps" -> "Launch the Java class dependency analyzer"; - case "jlink" -> "Assemble and optimize a set of modules into a custom runtime image"; - case "jmod" -> "Create JMOD files and list the content of existing JMOD files"; - case "jpackage" -> "Package a self-contained Java application"; - case "junit" -> "Launch the JUnit Platform"; - default -> provider.toString(); - }; - return "%s (provided by module %s)\n%s".formatted(name, by, info.indent(2)).trim(); - } - - static void list() { - var providers = bach().computeToolProviders().toList(); - var size = providers.size(); - providers.stream() - .map(tools::describe) - .sorted() - .map(description -> "\n" + description) - .forEach(out); - out("%n-> %d tool%s", size, size == 1 ? "" : "s"); - } - - static void runs() { - var list = bach().logbook().runs(); - var size = list.size(); - list.forEach(out); - out("%n-> %d run%s", size, size == 1 ? "" : "s"); - } - - static void run(String tool, Object... args) { - var command = Command.of(tool).addAll(args); - var recording = bach().run(command); - if (!recording.errors().isEmpty()) out.accept(recording.errors()); - if (!recording.output().isEmpty()) out.accept(recording.output()); - if (recording.isError()) - out.accept("Tool " + tool + " returned exit code " + recording.code()); - } - } - - public interface utils { - private static void describeClass(Class type) { - Stream.of(type.getDeclaredMethods()) - .filter(utils::describeOnlyInterestingMethods) - .sorted(Comparator.comparing(Method::getName).thenComparing(Method::getParameterCount)) - .map(utils::describeMethod) - .forEach(out); - list(type); - } - - private static boolean describeOnlyInterestingClasses(Class type) { - if (type.isRecord()) return false; - if (type.equals(utils.class)) return false; - var modifiers = type.getModifiers(); - return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers); - } - - private static boolean describeOnlyInterestingMethods(Method method) { - var modifiers = method.getModifiers(); - return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers); - } - - private static String describeMethod(Method method) { - var generic = method.toGenericString(); - var line = generic.replace('$', '.'); - var head = line.indexOf("bin.boot."); - if (head > 0) line = line.substring(head + 9); - var tail = line.indexOf(") throws"); - if (tail > 0) line = line.substring(0, tail + 1); - if (!line.endsWith("()")) { - line = line.replace("com.github.sormuras.bach.", ""); - line = line.replace("java.util.function.", ""); - line = line.replace("java.util.spi.", ""); - line = line.replace("java.util.", ""); - line = line.replace("java.lang.module.", ""); - line = line.replace("java.lang.", ""); - } - if (line.isEmpty()) throw new RuntimeException("Empty description line for: " + generic); - return line; - } - - static void api() { - list(boot.class); - } - - private static void list(Class current) { - Stream.of(current.getDeclaredClasses()) - .filter(utils::describeOnlyInterestingClasses) - .sorted(Comparator.comparing(Class::getName)) - .peek(declared -> out("")) - .forEach(utils::describeClass); - } - - static void refresh(String module) { - var load = Options.key(Property.BACH_INFO); - var options = Options.of(load, module); - try { - var bach = Bach.of(options); - set(bach); - } catch (Exception exception) { - out( - """ - - Refresh failed: %s - - Falling back to default Bach instance. - """, - exception.getMessage()); - set(new Bach(Options.of())); - } - } - - private static void set(Bach instance) { - bach.set(instance); - } - } - - private static final Consumer out = System.out::println; - private static final AtomicReference bach = new AtomicReference<>(); - - static { - refresh(); - } - - private static void out(Exception exception) { - out(""" - # - # %s - # - """, exception); - } - - private static void out(String format, Object... args) { - out.accept(args == null || args.length == 0 ? format : String.format(format, args)); - } - - /** Hidden default constructor. */ - private boot() {} -} diff --git a/.bach/bin/boot.jsh b/.bach/bin/boot.jsh deleted file mode 100644 index 0a4a828..0000000 --- a/.bach/bin/boot.jsh +++ /dev/null @@ -1,29 +0,0 @@ -// Bach's Boot Script - -System.out.println( -""" - , _ - /|/_) _, _ |) - | \\ / | / |/\\ - |(_/ \\/|_/ \\__/ | |/.boot - - Bach %s - Java Runtime %s - Operating System %s - Working Directory %s -""" -.formatted( - com.github.sormuras.bach.Bach.version(), - Runtime.version(), - System.getProperty("os.name"), - Path.of("").toAbsolutePath() -)) - -/reset - -import static bin.boot.* -import com.github.sormuras.bach.* - -void api() { utils.api(); } -void dir() { files.dir(); } -void pwd() { System.out.println(Path.of("").toAbsolutePath()); } diff --git a/.bach/bin/init.java b/.bach/bin/init.java deleted file mode 100644 index 8661f75..0000000 --- a/.bach/bin/init.java +++ /dev/null @@ -1,90 +0,0 @@ -import java.io.File; -import java.lang.module.ModuleFinder; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.List; - -/** Bach's init program. */ -class init { - - public static Path BIN = Path.of(".bach/bin"); - - public static void main(String... args) throws Exception { - var update = Files.isDirectory(BIN); - if (update) { - var module = ModuleFinder.of(BIN).find("com.github.sormuras.bach"); - if (module.isPresent()) Files.delete(Path.of(module.get().location().orElseThrow())); - } else { - Files.createDirectories(BIN); - } - - System.out.println(""); // Load scripts and modules... - var version = args.length == 0 ? "17-ea" : args[0]; - loadScript("bach").toFile().setExecutable(true); - loadScript("bach.bat"); - loadScript("boot.java"); - loadScript("boot.jsh"); - loadScript("init.java"); - loadModule("com.github.sormuras.bach", version); - - var appendPathMessage = - """ - - Append `%s` to the PATH environment variable in order to call - Bach's launch script without using that path prefix every time. - """ - .formatted(BIN); - var prefix = computePathPrefixToBachBinDirectory(() -> System.out.print(appendPathMessage)); - - if (!update) - System.out.printf( // On initialize, print possible next steps. - """ - - %sbach boot - Launch a JShell session with Bach booted into it. - %sbach --help - Print Bach's help message. - """, - prefix, prefix); - - System.out.printf( // Updated/Initialized Bach ${VERSION} in ${DIRECTORY}. - """ - - %sd Bach %s in %s. - """, - update ? "Update" : "Initialize", version, Path.of("").toAbsolutePath()); - } - - static String computePathPrefixToBachBinDirectory(Runnable runnable) { - var prefix = BIN.toString() + File.separator; - var path = System.getenv("PATH"); - if (path == null) return prefix; - var target = BIN.toString(); - var elements = List.of(path.split(File.pathSeparator)); - for (var element : elements) if (element.strip().equals(target)) return ""; - runnable.run(); - return prefix; - } - - static Path loadScript(String name) throws Exception { - var uri = "https://github.com/sormuras/bach/raw/main/.bach/bin/" + name; - return copy(uri, name); - } - - static void loadModule(String name, String version) throws Exception { - var jar = name + "@" + version + ".jar"; - var uri = "https://github.com/sormuras/bach/releases/download/" + version + "/" + jar; - copy(uri, jar); - } - - static Path copy(String uri, String name) throws Exception { - var file = BIN.resolve(name); - try (var stream = new URL(uri).openStream()) { - var size = Files.copy(stream, file, StandardCopyOption.REPLACE_EXISTING); - System.out.printf("%,7d %s%n", size, file); - } - return file; - } -} diff --git a/.gitignore b/.gitignore index 6751927..01f50f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +release/ target/ .idea/ *.iml diff --git a/README.md b/README.md index 0ed7ab0..aad8bdb 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,12 @@ Welcome to the World Clock application! This project is for a series of blog ent This is a standard Maven JavaFX modular (JPMS) application. ## Requirements: -- Maven 3.6.3 or greater (Optional) +- Maven 3.6.3 or greater - Java 16+ -- Bach (https://github.com/sormuras/bach) +- Bach (https://github.com/sormuras/bach) (Optional) + +It's recommended if you use a JDK distro that already contains the OpenJFX libraries such as Azul's Zulu builds with JavaFX. +If not, then you must uncomment the dependencies needed in the pom.xml file. ## Build project using Bach @@ -30,11 +33,11 @@ On Windows you'll add to your environment variables as the following: `set PATH=%PATH%;.bach\bin` -## Run World Clock as a module +### Run World Clock as a module `$ java --add-modules worldclock --module-path .bach/workspace/modules/:.bach/external-modules/ com.carlfx.worldclock.Launcher` -## Run World Clock using a custom image +### Run World Clock using a custom image ```bash # Linux/MacOS @@ -51,23 +54,27 @@ $ .bach\workspace\image\bin\worldclock `$ mvn clean` ## Run World Clock using Maven plugin +This will compile and build the JavaFX project locally using the Maven plugin. `$ mvn javafx:run` -## Create a MacOS package to run allow user to install onto desktop +## Create a custom image (Java runtime w/world clock executable) +`$ mvn javafx:jlink` +## Create a MacOS package to run allow user to install onto desktop +The following assumes the prior step completed successfully. The runtime artifacts reside in the target/worldclock directory. ```bash $ jpackage --verbose \ --name "JFX World Clock" \ --description "JavaFX World Clock Application" \ --vendor "Carl Dea" \ - --runtime-image .bach/workspace/image \ + --runtime-image target/worldclock/ \ --module worldclock/com.carlfx.worldclock.Launcher \ - --dest .bach/workspace/package + --dest release ``` # Outstanding issues 1. The map doesn't load properly when building and running the app as a built image. 2. Map uses MapBox and Leaflet.js an access_token needs to be created. See https://www.mapbox.com/studio/account/tokens/ for details. -3. Weather is still under construction. This may need an access_token also. +3. Weather is still under construction. This may need an access_token too. diff --git a/pom.xml b/pom.xml index ca336b2..a27b952 100644 --- a/pom.xml +++ b/pom.xml @@ -10,21 +10,22 @@ 16 - - org.openjfx - javafx-controls - ${javafx.version} - - - org.openjfx - javafx-web - ${javafx.version} - - - org.openjfx - javafx-fxml - ${javafx.version} - + + + + + + + + + + + + + + + + com.fasterxml.jackson.core jackson-core diff --git a/src/main/java/com/carlfx/worldclock/App.java b/src/main/java/com/carlfx/worldclock/App.java index deccd78..f4b87c5 100644 --- a/src/main/java/com/carlfx/worldclock/App.java +++ b/src/main/java/com/carlfx/worldclock/App.java @@ -46,6 +46,8 @@ import javafx.util.Duration; import java.io.IOException; import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; import static com.carlfx.worldclock.WorldClockEvent.*; @@ -125,39 +127,27 @@ public class App extends Application { windowContainer.setCenter(centerPane); makeDraggable(windowContainer); - WebView webView = createMap(); - // load each location clock face - List clocks = new ArrayList<>(); - final WebEngine webEngine = webView.getEngine(); - webEngine.setJavaScriptEnabled(true); - webEngine.setOnAlert(webEvent -> { - System.out.println(webEvent); - System.out.println(webEvent.getData()); + // Create a WebView (index.html) containing a map. After map is finished loading add the pins of locations. + WebView webView = createMap((webEngine) -> { + for (Location location:locations) { + // Call JS function addMarker to add pins to the map based on locations. + webEngine.executeScript( + "addMarker(\"%s\", %3.8f, %3.8f)".formatted(location.getFullLocationName(), + location.getLatitude(), location.getLongitude())); + } + centerPane.setPrefHeight(configPane.getBoundsInLocal().getHeight()); }); - webEngine.getLoadWorker() - .stateProperty() - .addListener((observable, oldValue, newValue) -> { - if (newValue == Worker.State.SUCCEEDED) { - for(Location location:locations) { - webEngine.executeScript(String.format("addMarker(\"%s\", %3.8f, %3.8f)", - location.getCity(), location.getLatitude(), location.getLongitude())); - } - centerPane.setPrefHeight(configPane.getBoundsInLocal().getHeight()); - System.out.println("config size " + configPane.getBoundsInLocal().getHeight()); - System.out.println("clockList one clock height " + clockList.getChildren().get(0).getBoundsInLocal().getHeight()); - System.out.println("clockList width " + clockList.getWidth()); - System.out.println("config width " + configPane.getBoundsInLocal().getWidth()); - } - } - ); - for(Location location:locations) { - // each controller will attach a listener for cleanup code. - clocks.add(createOneClock(webEngine, location)); - } + // Obtain web engine + final WebEngine webEngine = webView.getEngine(); + + // Style the vbox clockList.getStyleClass().add("clock-background"); clockList.getChildren() - .addAll(clocks); + .addAll(locations.stream() + .map( location -> createOneClock(webEngine, location)) /* Create clock from FXML */ + .collect(Collectors.toList())); + scrollPane.getStyleClass().add("clock-background"); // Animate toggle between Config view vs World Clock List view @@ -205,8 +195,8 @@ public class App extends Application { windowContainer.addEventHandler(LOCATION_ADD, event -> { Location location = event.getPayload(); // Add a map marker - webEngine.executeScript(String.format("addMarker(\"%s\", %3.8f, %3.8f)", - location.getCity(), location.getLatitude(), location.getLongitude())); + webEngine.executeScript("addMarker(\"%s\", %3.8f, %3.8f)".formatted( + location.getFullLocationName(), location.getLatitude(), location.getLongitude())); // Make VBox visual rows(HBox) are in the same order. Iterator itr = clockList.getChildren().iterator(); @@ -221,25 +211,31 @@ public class App extends Application { } } + Parent oneClockView = createOneClock(webEngine, location); - try { - Parent oneClockView = createOneClock(webEngine, location); - - if (found && clockList.getChildren().size()-1 > 0) { - // replace with new location - clockList.getChildren().set(idx, oneClockView); - } else { - clockList.getChildren().add(oneClockView); - } - } catch (IOException e) { - e.printStackTrace(); + if (found && clockList.getChildren().size()-1 > 0) { + // replace with new location + clockList.getChildren().set(idx, oneClockView); + } else { + clockList.getChildren().add(oneClockView); } + + }); + + // When location (lat/lon) changes remove old pin (marker) on map and replace + windowContainer.addEventHandler(LOCATION_UPDATE, event -> { + Location location = event.getPayload(); + // Add a map marker + webEngine.executeScript("addMarker(\"%s\", %3.8f, %3.8f)".formatted( + location.getFullLocationName(), location.getLatitude(), location.getLongitude())); }); // Subscribe to a removed Location event windowContainer.addEventFilter(LOCATION_REMOVE, event -> { Location location = event.getPayload(); System.out.println("window container location_remove heard!"); + // Remove Pin a map marker + webEngine.executeScript("removeMarker(\"%s\")".formatted(location.getFullLocationName())); Iterator itr = clockList.getChildren().iterator(); while (itr.hasNext()) { @@ -294,17 +290,21 @@ public class App extends Application { stage.show(); } - private Parent createOneClock(WebEngine webEngine, Location location) throws IOException { - Parent oneClockView = loadClockFXML(location); - oneClockView.setOnMouseClicked(mouseEvent -> { - if (mouseEvent.getClickCount() == 2) { - String jsCall = String.format("viewMapLocation(%3.8f, %3.8f)", location.getLatitude(), location.getLongitude()); - System.out.println(jsCall); - webEngine.executeScript(jsCall); - System.out.println(location); - } - }); - return oneClockView; + private Parent createOneClock(WebEngine webEngine, Location location) { + Parent oneClockView = null; + try { + oneClockView = loadClockFXML(location); + oneClockView.setOnMouseClicked(mouseEvent -> { + if (mouseEvent.getClickCount() == 2) { + String jsCall = "viewMapLocation('%s')".formatted(location.getFullLocationName()); + webEngine.executeScript(jsCall); + } + }); + return oneClockView; + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } } private Optional grabScrollBar(ScrollPane scrollPane, Orientation orientation) { @@ -332,25 +332,30 @@ public class App extends Application { * A simple WebView containing a web page. * @return */ - private WebView createMap() { + private WebView createMap(Consumer addPointMarkers) { WebView webView = new WebView(); webView.setPrefWidth(310); webView.setPrefHeight(310); WebEngine webEngine = webView.getEngine(); - // Enable Javascript. + // Allows Java code to talk to JavaScript code webEngine.setJavaScriptEnabled(true); - webEngine.getLoadWorker().stateProperty().addListener( - (observable, oldValue, newValue) -> { + webEngine.setOnAlert(webEvent -> { + System.out.println("WebKit Alert: " + webEvent.getData()); + }); + webEngine + .getLoadWorker() + .stateProperty().addListener((observable, oldValue, newValue) -> { if (newValue == Worker.State.SUCCEEDED) { System.out.println("index.html loaded successfully"); + addPointMarkers.accept(webEngine); } else { System.out.println("WebEngine state " + newValue); } } ); - webEngine.load(Objects.requireNonNull(getClass().getResource("index.html")).toExternalForm()); + webEngine.load(getClass().getResource("index.html").toExternalForm()); webView.requestFocus(); return webView; diff --git a/src/main/java/com/carlfx/worldclock/ConfigLocationsController.java b/src/main/java/com/carlfx/worldclock/ConfigLocationsController.java index d5ec43a..ed77409 100644 --- a/src/main/java/com/carlfx/worldclock/ConfigLocationsController.java +++ b/src/main/java/com/carlfx/worldclock/ConfigLocationsController.java @@ -135,7 +135,7 @@ public class ConfigLocationsController { if (!latitude.getText().isBlank() && !longitude.getText().isBlank()) { location.setLatLong(latitude.getText(), longitude.getText()); } - + WorldClockEvent.trigger(longitude, WorldClockEvent.LOCATION_UPDATE, location); } else { if (stateSelected != null && !"".equals(stateSelected)) { diff --git a/src/main/java/com/carlfx/worldclock/WorldClockEvent.java b/src/main/java/com/carlfx/worldclock/WorldClockEvent.java index 85d8e7d..3ec8b1f 100644 --- a/src/main/java/com/carlfx/worldclock/WorldClockEvent.java +++ b/src/main/java/com/carlfx/worldclock/WorldClockEvent.java @@ -34,6 +34,7 @@ public class WorldClockEvent extends Event { public static final EventType CONFIG_HIDDEN = new EventType("CONFIG_HIDDEN"); public static final EventType MAIN_APP_CLOSE = new EventType("MAIN_APP_CLOSE"); public static final EventType LOCATION_ADD = new EventType("LOCATION_ADD"); + public static final EventType LOCATION_UPDATE = new EventType("LOCATION_UPDATE"); public static final EventType LOCATION_REMOVE = new EventType("LOCATION_REMOVE"); public static final EventType LOCATION_MOVE_UP = new EventType("LOCATION_MOVE_UP"); public static final EventType LOCATION_MOVE_DOWN = new EventType("LOCATION_MOVE_DOWN"); diff --git a/src/main/resources/com/carlfx/worldclock/images/layers-2x.png b/src/main/resources/com/carlfx/worldclock/images/layers-2x.png new file mode 100644 index 0000000..200c333 Binary files /dev/null and b/src/main/resources/com/carlfx/worldclock/images/layers-2x.png differ diff --git a/src/main/resources/com/carlfx/worldclock/images/layers.png b/src/main/resources/com/carlfx/worldclock/images/layers.png new file mode 100644 index 0000000..1a72e57 Binary files /dev/null and b/src/main/resources/com/carlfx/worldclock/images/layers.png differ diff --git a/src/main/resources/com/carlfx/worldclock/images/marker-icon-2x.png b/src/main/resources/com/carlfx/worldclock/images/marker-icon-2x.png new file mode 100644 index 0000000..88f9e50 Binary files /dev/null and b/src/main/resources/com/carlfx/worldclock/images/marker-icon-2x.png differ diff --git a/src/main/resources/com/carlfx/worldclock/images/marker-icon.png b/src/main/resources/com/carlfx/worldclock/images/marker-icon.png new file mode 100644 index 0000000..950edf2 Binary files /dev/null and b/src/main/resources/com/carlfx/worldclock/images/marker-icon.png differ diff --git a/src/main/resources/com/carlfx/worldclock/images/marker-shadow.png b/src/main/resources/com/carlfx/worldclock/images/marker-shadow.png new file mode 100644 index 0000000..9fd2979 Binary files /dev/null and b/src/main/resources/com/carlfx/worldclock/images/marker-shadow.png differ diff --git a/src/main/resources/com/carlfx/worldclock/index.html b/src/main/resources/com/carlfx/worldclock/index.html index 2fccc29..9a3696b 100644 --- a/src/main/resources/com/carlfx/worldclock/index.html +++ b/src/main/resources/com/carlfx/worldclock/index.html @@ -1,60 +1,200 @@ - - Quick Start - Leaflet + JavaFX World Clock - + - -
+
\ No newline at end of file diff --git a/src/main/resources/com/carlfx/worldclock/leaflet.css b/src/main/resources/com/carlfx/worldclock/leaflet.css new file mode 100644 index 0000000..601476f --- /dev/null +++ b/src/main/resources/com/carlfx/worldclock/leaflet.css @@ -0,0 +1,640 @@ +/* required styles */ + +.leaflet-pane, +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-tile-container, +.leaflet-pane > svg, +.leaflet-pane > canvas, +.leaflet-zoom-box, +.leaflet-image-layer, +.leaflet-layer { + position: absolute; + left: 0; + top: 0; + } +.leaflet-container { + overflow: hidden; + } +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none; + } +/* Prevents IE11 from highlighting tiles in blue */ +.leaflet-tile::selection { + background: transparent; +} +/* Safari renders non-retina tile on retina better with this, but Chrome is worse */ +.leaflet-safari .leaflet-tile { + image-rendering: -webkit-optimize-contrast; + } +/* hack that prevents hw layers "stretching" when loading new tiles */ +.leaflet-safari .leaflet-tile-container { + width: 1600px; + height: 1600px; + -webkit-transform-origin: 0 0; + } +.leaflet-marker-icon, +.leaflet-marker-shadow { + display: block; + } +/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ +/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ +.leaflet-container .leaflet-overlay-pane svg, +.leaflet-container .leaflet-marker-pane img, +.leaflet-container .leaflet-shadow-pane img, +.leaflet-container .leaflet-tile-pane img, +.leaflet-container img.leaflet-image-layer, +.leaflet-container .leaflet-tile { + max-width: none !important; + max-height: none !important; + } + +.leaflet-container.leaflet-touch-zoom { + -ms-touch-action: pan-x pan-y; + touch-action: pan-x pan-y; + } +.leaflet-container.leaflet-touch-drag { + -ms-touch-action: pinch-zoom; + /* Fallback for FF which doesn't support pinch-zoom */ + touch-action: none; + touch-action: pinch-zoom; +} +.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { + -ms-touch-action: none; + touch-action: none; +} +.leaflet-container { + -webkit-tap-highlight-color: transparent; +} +.leaflet-container a { + -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); +} +.leaflet-tile { + filter: inherit; + visibility: hidden; + } +.leaflet-tile-loaded { + visibility: inherit; + } +.leaflet-zoom-box { + width: 0; + height: 0; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 800; + } +/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ +.leaflet-overlay-pane svg { + -moz-user-select: none; + } + +.leaflet-pane { z-index: 400; } + +.leaflet-tile-pane { z-index: 200; } +.leaflet-overlay-pane { z-index: 400; } +.leaflet-shadow-pane { z-index: 500; } +.leaflet-marker-pane { z-index: 600; } +.leaflet-tooltip-pane { z-index: 650; } +.leaflet-popup-pane { z-index: 700; } + +.leaflet-map-pane canvas { z-index: 100; } +.leaflet-map-pane svg { z-index: 200; } + +.leaflet-vml-shape { + width: 1px; + height: 1px; + } +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; + } + + +/* control positioning */ + +.leaflet-control { + position: relative; + z-index: 800; + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } +.leaflet-top, +.leaflet-bottom { + position: absolute; + z-index: 1000; + pointer-events: none; + } +.leaflet-top { + top: 0; + } +.leaflet-right { + right: 0; + } +.leaflet-bottom { + bottom: 0; + } +.leaflet-left { + left: 0; + } +.leaflet-control { + float: left; + clear: both; + } +.leaflet-right .leaflet-control { + float: right; + } +.leaflet-top .leaflet-control { + margin-top: 10px; + } +.leaflet-bottom .leaflet-control { + margin-bottom: 10px; + } +.leaflet-left .leaflet-control { + margin-left: 10px; + } +.leaflet-right .leaflet-control { + margin-right: 10px; + } + + +/* zoom and fade animations */ + +.leaflet-fade-anim .leaflet-tile { + will-change: opacity; + } +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + } +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1; + } +.leaflet-zoom-animated { + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + will-change: transform; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); + -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); + transition: transform 0.25s cubic-bezier(0,0,0.25,1); + } +.leaflet-zoom-anim .leaflet-tile, +.leaflet-pan-anim .leaflet-tile { + -webkit-transition: none; + -moz-transition: none; + transition: none; + } + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden; + } + + +/* cursors */ + +.leaflet-interactive { + cursor: pointer; + } +.leaflet-grab { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; + } +.leaflet-crosshair, +.leaflet-crosshair .leaflet-interactive { + cursor: crosshair; + } +.leaflet-popup-pane, +.leaflet-control { + cursor: auto; + } +.leaflet-dragging .leaflet-grab, +.leaflet-dragging .leaflet-grab .leaflet-interactive, +.leaflet-dragging .leaflet-marker-draggable { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; + } + +/* marker & overlays interactivity */ +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-image-layer, +.leaflet-pane > svg path, +.leaflet-tile-container { + pointer-events: none; + } + +.leaflet-marker-icon.leaflet-interactive, +.leaflet-image-layer.leaflet-interactive, +.leaflet-pane > svg path.leaflet-interactive, +svg.leaflet-image-layer.leaflet-interactive path { + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } + +/* visual tweaks */ + +.leaflet-container { + background: #ddd; + outline: 0; + } +.leaflet-container a { + color: #0078A8; + } +.leaflet-container a.leaflet-active { + outline: 2px solid orange; + } +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255,255,255,0.5); + } + + +/* general typography */ +.leaflet-container { + font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; + } + + +/* general toolbar styles */ + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0,0,0,0.65); + border-radius: 4px; + } +.leaflet-bar a, +.leaflet-bar a:hover { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; + } +.leaflet-bar a, +.leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block; + } +.leaflet-bar a:hover { + background-color: #f4f4f4; + } +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; + } +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb; + } + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px; + } +.leaflet-touch .leaflet-bar a:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px; + } +.leaflet-touch .leaflet-bar a:last-child { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + } + +/* zoom control */ + +.leaflet-control-zoom-in, +.leaflet-control-zoom-out { + font: bold 18px 'Lucida Console', Monaco, monospace; + text-indent: 1px; + } + +.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { + font-size: 22px; + } + + +/* layers control */ + +.leaflet-control-layers { + box-shadow: 0 1px 5px rgba(0,0,0,0.4); + background: #fff; + border-radius: 5px; + } +.leaflet-control-layers-toggle { + background-image: url(images/layers.png); + width: 36px; + height: 36px; + } +.leaflet-retina .leaflet-control-layers-toggle { + background-image: url(images/layers-2x.png); + background-size: 26px 26px; + } +.leaflet-touch .leaflet-control-layers-toggle { + width: 44px; + height: 44px; + } +.leaflet-control-layers .leaflet-control-layers-list, +.leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; + } +.leaflet-control-layers-expanded .leaflet-control-layers-list { + display: block; + position: relative; + } +.leaflet-control-layers-expanded { + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; + } +.leaflet-control-layers-scrollbar { + overflow-y: scroll; + overflow-x: hidden; + padding-right: 5px; + } +.leaflet-control-layers-selector { + margin-top: 2px; + position: relative; + top: 1px; + } +.leaflet-control-layers label { + display: block; + } +.leaflet-control-layers-separator { + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; + } + +/* Default icon URLs */ +.leaflet-default-icon-path { + background-image: url(images/marker-icon.png); + } + + +/* attribution and scale controls */ + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, 0.7); + margin: 0; + } +.leaflet-control-attribution, +.leaflet-control-scale-line { + padding: 0 5px; + color: #333; + } +.leaflet-control-attribution a { + text-decoration: none; + } +.leaflet-control-attribution a:hover { + text-decoration: underline; + } +.leaflet-container .leaflet-control-attribution, +.leaflet-container .leaflet-control-scale { + font-size: 11px; + } +.leaflet-left .leaflet-control-scale { + margin-left: 5px; + } +.leaflet-bottom .leaflet-control-scale { + margin-bottom: 5px; + } +.leaflet-control-scale-line { + border: 2px solid #777; + border-top: none; + line-height: 1.1; + padding: 2px 5px 1px; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + -moz-box-sizing: border-box; + box-sizing: border-box; + + background: #fff; + background: rgba(255, 255, 255, 0.5); + } +.leaflet-control-scale-line:not(:first-child) { + border-top: 2px solid #777; + border-bottom: none; + margin-top: -2px; + } +.leaflet-control-scale-line:not(:first-child):not(:last-child) { + border-bottom: 2px solid #777; + } + +.leaflet-touch .leaflet-control-attribution, +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + box-shadow: none; + } +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + border: 2px solid rgba(0,0,0,0.2); + background-clip: padding-box; + } + + +/* popup */ + +.leaflet-popup { + position: absolute; + text-align: center; + margin-bottom: 20px; + } +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px; + } +.leaflet-popup-content { + margin: 13px 19px; + line-height: 1.4; + } +.leaflet-popup-content p { + margin: 18px 0; + } +.leaflet-popup-tip-container { + width: 40px; + height: 20px; + position: absolute; + left: 50%; + margin-left: -20px; + overflow: hidden; + pointer-events: none; + } +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + + margin: -10px auto 0; + + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + } +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: white; + color: #333; + box-shadow: 0 3px 14px rgba(0,0,0,0.4); + } +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + padding: 4px 4px 0 0; + border: none; + text-align: center; + width: 18px; + height: 14px; + font: 16px/14px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: bold; + background: transparent; + } +.leaflet-container a.leaflet-popup-close-button:hover { + color: #999; + } +.leaflet-popup-scrolled { + overflow: auto; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + } + +.leaflet-oldie .leaflet-popup-content-wrapper { + -ms-zoom: 1; + } +.leaflet-oldie .leaflet-popup-tip { + width: 24px; + margin: 0 auto; + + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); + } +.leaflet-oldie .leaflet-popup-tip-container { + margin-top: -1px; + } + +.leaflet-oldie .leaflet-control-zoom, +.leaflet-oldie .leaflet-control-layers, +.leaflet-oldie .leaflet-popup-content-wrapper, +.leaflet-oldie .leaflet-popup-tip { + border: 1px solid #999; + } + + +/* div icon */ + +.leaflet-div-icon { + background: #fff; + border: 1px solid #666; + } + + +/* Tooltip */ +/* Base styles for the element that has a tooltip */ +.leaflet-tooltip { + position: absolute; + padding: 6px; + background-color: #fff; + border: 1px solid #fff; + border-radius: 3px; + color: #222; + white-space: nowrap; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; + box-shadow: 0 1px 3px rgba(0,0,0,0.4); + } +.leaflet-tooltip.leaflet-clickable { + cursor: pointer; + pointer-events: auto; + } +.leaflet-tooltip-top:before, +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + position: absolute; + pointer-events: none; + border: 6px solid transparent; + background: transparent; + content: ""; + } + +/* Directions */ + +.leaflet-tooltip-bottom { + margin-top: 6px; +} +.leaflet-tooltip-top { + margin-top: -6px; +} +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-top:before { + left: 50%; + margin-left: -6px; + } +.leaflet-tooltip-top:before { + bottom: 0; + margin-bottom: -12px; + border-top-color: #fff; + } +.leaflet-tooltip-bottom:before { + top: 0; + margin-top: -12px; + margin-left: -6px; + border-bottom-color: #fff; + } +.leaflet-tooltip-left { + margin-left: -6px; +} +.leaflet-tooltip-right { + margin-left: 6px; +} +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + top: 50%; + margin-top: -6px; + } +.leaflet-tooltip-left:before { + right: 0; + margin-right: -12px; + border-left-color: #fff; + } +.leaflet-tooltip-right:before { + left: 0; + margin-left: -12px; + border-right-color: #fff; + } diff --git a/src/main/resources/com/carlfx/worldclock/leaflet.js b/src/main/resources/com/carlfx/worldclock/leaflet.js new file mode 100644 index 0000000..21f499c --- /dev/null +++ b/src/main/resources/com/carlfx/worldclock/leaflet.js @@ -0,0 +1,6 @@ +/* @preserve + * Leaflet 1.7.1, a JS library for interactive maps. http://leafletjs.com + * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade + */ +!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i(t.L={})}(this,function(t){"use strict";function h(t){for(var i,e,n=1,o=arguments.length;n=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=O(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>=i.x&&n.x<=e.x,r=o.y>=i.y&&n.y<=e.y;return s&&r},overlaps:function(t){t=O(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>i.x&&n.xi.y&&n.y=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=N(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>=i.lat&&n.lat<=e.lat,r=o.lng>=i.lng&&n.lng<=e.lng;return s&&r},overlaps:function(t){t=N(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>i.lat&&n.lati.lng&&n.lng';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}();function kt(t){return 0<=navigator.userAgent.toLowerCase().indexOf(t)}var Bt={ie:tt,ielt9:it,edge:et,webkit:nt,android:ot,android23:st,androidStock:at,opera:ht,chrome:ut,gecko:lt,safari:ct,phantom:_t,opera12:dt,win:pt,ie3d:mt,webkit3d:ft,gecko3d:gt,any3d:vt,mobile:yt,mobileWebkit:xt,mobileWebkit3d:wt,msPointer:Pt,pointer:Lt,touch:bt,mobileOpera:Tt,mobileGecko:Mt,retina:zt,passiveEvents:Ct,canvas:St,svg:Zt,vml:Et},At=Pt?"MSPointerDown":"pointerdown",It=Pt?"MSPointerMove":"pointermove",Ot=Pt?"MSPointerUp":"pointerup",Rt=Pt?"MSPointerCancel":"pointercancel",Nt={},Dt=!1;function jt(t,i,e,n){function o(t){Ut(t,r)}var s,r,a,h,u,l,c,_;function d(t){t.pointerType===(t.MSPOINTER_TYPE_MOUSE||"mouse")&&0===t.buttons||Ut(t,h)}return"touchstart"===i?(u=t,l=e,c=n,_=p(function(t){t.MSPOINTER_TYPE_TOUCH&&t.pointerType===t.MSPOINTER_TYPE_TOUCH&&Ri(t),Ut(t,l)}),u["_leaflet_touchstart"+c]=_,u.addEventListener(At,_,!1),Dt||(document.addEventListener(At,Wt,!0),document.addEventListener(It,Ht,!0),document.addEventListener(Ot,Ft,!0),document.addEventListener(Rt,Ft,!0),Dt=!0)):"touchmove"===i?(h=e,(a=t)["_leaflet_touchmove"+n]=d,a.addEventListener(It,d,!1)):"touchend"===i&&(r=e,(s=t)["_leaflet_touchend"+n]=o,s.addEventListener(Ot,o,!1),s.addEventListener(Rt,o,!1)),this}function Wt(t){Nt[t.pointerId]=t}function Ht(t){Nt[t.pointerId]&&(Nt[t.pointerId]=t)}function Ft(t){delete Nt[t.pointerId]}function Ut(t,i){for(var e in t.touches=[],Nt)t.touches.push(Nt[e]);t.changedTouches=[t],i(t)}var Vt=Pt?"MSPointerDown":Lt?"pointerdown":"touchstart",qt=Pt?"MSPointerUp":Lt?"pointerup":"touchend",Gt="_leaflet_";var Kt,Yt,Xt,Jt,$t,Qt,ti=fi(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),ii=fi(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),ei="webkitTransition"===ii||"OTransition"===ii?ii+"End":"transitionend";function ni(t){return"string"==typeof t?document.getElementById(t):t}function oi(t,i){var e,n=t.style[i]||t.currentStyle&&t.currentStyle[i];return n&&"auto"!==n||!document.defaultView||(n=(e=document.defaultView.getComputedStyle(t,null))?e[i]:null),"auto"===n?null:n}function si(t,i,e){var n=document.createElement(t);return n.className=i||"",e&&e.appendChild(n),n}function ri(t){var i=t.parentNode;i&&i.removeChild(t)}function ai(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function hi(t){var i=t.parentNode;i&&i.lastChild!==t&&i.appendChild(t)}function ui(t){var i=t.parentNode;i&&i.firstChild!==t&&i.insertBefore(t,i.firstChild)}function li(t,i){if(void 0!==t.classList)return t.classList.contains(i);var e=pi(t);return 0this.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),n=this._limitCenter(e,this._zoom,N(t));return e.equals(n)||this.panTo(n,i),this._enforcingBounds=!1,this},panInside:function(t,i){var e,n,o=A((i=i||{}).paddingTopLeft||i.padding||[0,0]),s=A(i.paddingBottomRight||i.padding||[0,0]),r=this.getCenter(),a=this.project(r),h=this.project(t),u=this.getPixelBounds(),l=u.getSize().divideBy(2),c=O([u.min.add(o),u.max.subtract(s)]);return c.contains(h)||(this._enforcingBounds=!0,e=a.subtract(h),n=A(h.x+e.x,h.y+e.y),(h.xc.max.x)&&(n.x=a.x-e.x,0c.max.y)&&(n.y=a.y-e.y,0=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,n=[],o="mouseout"===i||"mouseover"===i,s=t.target||t.srcElement,r=!1;s;){if((e=this._targets[m(s)])&&("click"===i||"preclick"===i)&&!t._simulated&&this._draggableMoved(e)){r=!0;break}if(e&&e.listens(i,!0)){if(o&&!Vi(s,t))break;if(n.push(e),o)break}if(s===this._container)break;s=s.parentNode}return n.length||r||o||!Vi(s,t)||(n=[this]),n},_handleDOMEvent:function(t){var i;this._loaded&&!Ui(t)&&("mousedown"!==(i=t.type)&&"keypress"!==i&&"keyup"!==i&&"keydown"!==i||Pi(t.target||t.srcElement),this._fireDOMEvent(t,i))},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,i,e){var n;if("click"===t.type&&((n=h({},t)).type="preclick",this._fireDOMEvent(n,n.type,e)),!t._stopped&&(e=(e||[]).concat(this._findEventTargets(t,i))).length){var o=e[0];"contextmenu"===i&&o.listens(i,!0)&&Ri(t);var s,r={originalEvent:t};"keypress"!==t.type&&"keydown"!==t.type&&"keyup"!==t.type&&(s=o.getLatLng&&(!o._radius||o._radius<=10),r.containerPoint=s?this.latLngToContainerPoint(o.getLatLng()):this.mouseEventToContainerPoint(t),r.layerPoint=this.containerPointToLayerPoint(r.containerPoint),r.latlng=s?o.getLatLng():this.layerPointToLatLng(r.layerPoint));for(var a=0;athis.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),o=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(o))&&(M(function(){this._moveStart(!0,!1)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,e,n){this._mapPane&&(e&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,ci(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:i,noUpdate:n}),setTimeout(p(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&_i(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),M(function(){this._moveEnd(!0)},this))}});function Yi(t){return new Xi(t)}var Xi=S.extend({options:{position:"topright"},initialize:function(t){c(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),n=t._controlCorners[e];return ci(i,"leaflet-control"),-1!==e.indexOf("bottom")?n.insertBefore(i,n.firstChild):n.appendChild(i),this._map.on("unload",this.remove,this),this},remove:function(){return this._map&&(ri(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null),this},_refocusOnMap:function(t){this._map&&t&&0",n=document.createElement("div");return n.innerHTML=e,n.firstChild},_addItem:function(t){var i,e=document.createElement("label"),n=this._map.hasLayer(t.layer);t.overlay?((i=document.createElement("input")).type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=n):i=this._createRadioElement("leaflet-base-layers_"+m(this),n),this._layerControlInputs.push(i),i.layerId=m(t.layer),zi(i,"click",this._onInputClick,this);var o=document.createElement("span");o.innerHTML=" "+t.name;var s=document.createElement("div");return e.appendChild(s),s.appendChild(i),s.appendChild(o),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;0<=s;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;si.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),$i=Xi.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"−",zoomOutTitle:"Zoom out"},onAdd:function(t){var i="leaflet-control-zoom",e=si("div",i+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+"-in",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+"-out",e,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),e},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){var s=si("a",e,n);return s.innerHTML=t,s.href="#",s.title=i,s.setAttribute("role","button"),s.setAttribute("aria-label",i),Oi(s),zi(s,"click",Ni),zi(s,"click",o,this),zi(s,"click",this._refocusOnMap,this),s},_updateDisabled:function(){var t=this._map,i="leaflet-disabled";_i(this._zoomInButton,i),_i(this._zoomOutButton,i),!this._disabled&&t._zoom!==t.getMinZoom()||ci(this._zoomOutButton,i),!this._disabled&&t._zoom!==t.getMaxZoom()||ci(this._zoomInButton,i)}});Ki.mergeOptions({zoomControl:!0}),Ki.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new $i,this.addControl(this.zoomControl))});var Qi=Xi.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i="leaflet-control-scale",e=si("div",i),n=this.options;return this._addScales(n,i+"-line",e),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),e},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=si("div",i,e)),t.imperial&&(this._iScale=si("div",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,e=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(e)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t),e=i<1e3?i+" m":i/1e3+" km";this._updateScale(this._mScale,e,i/t)},_updateImperial:function(t){var i,e,n,o=3.2808399*t;5280Leaflet'},initialize:function(t){c(this,t),this._attributions={}},onAdd:function(t){for(var i in(t.attributionControl=this)._container=si("div","leaflet-control-attribution"),Oi(this._container),t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t&&(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update()),this},removeAttribution:function(t){return t&&this._attributions[t]&&(this._attributions[t]--,this._update()),this},_update:function(){if(this._map){var t=[];for(var i in this._attributions)this._attributions[i]&&t.push(i);var e=[];this.options.prefix&&e.push(this.options.prefix),t.length&&e.push(t.join(", ")),this._container.innerHTML=e.join(" | ")}}});Ki.mergeOptions({attributionControl:!0}),Ki.addInitHook(function(){this.options.attributionControl&&(new te).addTo(this)});Xi.Layers=Ji,Xi.Zoom=$i,Xi.Scale=Qi,Xi.Attribution=te,Yi.layers=function(t,i,e){return new Ji(t,i,e)},Yi.zoom=function(t){return new $i(t)},Yi.scale=function(t){return new Qi(t)},Yi.attribution=function(t){return new te(t)};var ie=S.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled&&(this._enabled=!1,this.removeHooks()),this},enabled:function(){return!!this._enabled}});ie.addTo=function(t,i){return t.addHandler(i,this),this};var ee,ne={Events:Z},oe=bt?"touchstart mousedown":"mousedown",se={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},re={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"},ae=E.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){c(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(zi(this._dragStartTarget,oe,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(ae._dragging===this&&this.finishDrag(),Si(this._dragStartTarget,oe,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){var i,e;!t._simulated&&this._enabled&&(this._moved=!1,li(this._element,"leaflet-zoom-anim")||ae._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||((ae._dragging=this)._preventOutline&&Pi(this._element),xi(),Xt(),this._moving||(this.fire("down"),i=t.touches?t.touches[0]:t,e=bi(this._element),this._startPoint=new k(i.clientX,i.clientY),this._parentScale=Ti(e),zi(document,re[t.type],this._onMove,this),zi(document,se[t.type],this._onUp,this))))},_onMove:function(t){var i,e;!t._simulated&&this._enabled&&(t.touches&&1i&&(e.push(t[n]),o=n);oi.max.x&&(e|=2),t.yi.max.y&&(e|=8),e}function de(t,i,e,n){var o,s=i.x,r=i.y,a=e.x-s,h=e.y-r,u=a*a+h*h;return 0this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()t.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(u=!u);return u||Oe.prototype._containsPoint.call(this,t,!0)}});var Ne=Ce.extend({initialize:function(t,i){c(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=g(t)?t:t.features;if(o){for(i=0,e=o.length;iu.x&&(l=s.x+n-u.x+h.x),s.x-l-a.x<0&&(l=s.x-a.x),s.y+e+h.y>u.y&&(c=s.y+e-u.y+h.y),s.y-c-a.y<0&&(c=s.y-a.y),(l||c)&&t.fire("autopanstart").panBy([l,c]))},_onCloseButtonClick:function(t){this._close(),Ni(t)},_getAnchor:function(){return A(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}});Ki.mergeOptions({closePopupOnClick:!0}),Ki.include({openPopup:function(t,i,e){return t instanceof tn||(t=new tn(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),Me.include({bindPopup:function(t,i){return t instanceof tn?(c(t,i),(this._popup=t)._source=this):(this._popup&&!i||(this._popup=new tn(i,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,i){return this._popup&&this._map&&(i=this._popup._prepareOpen(this,t,i),this._map.openPopup(this._popup,i)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i=t.layer||t.target;this._popup&&this._map&&(Ni(t),i instanceof Be?this.openPopup(t.layer||t.target,t.latlng):this._map.hasLayer(this._popup)&&this._popup._source===i?this.closePopup():this.openPopup(i,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}});var en=Qe.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){Qe.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){Qe.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=Qe.prototype.getEvents.call(this);return bt&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=si("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i,e=this._map,n=this._container,o=e.latLngToContainerPoint(e.getCenter()),s=e.layerPointToContainerPoint(t),r=this.options.direction,a=n.offsetWidth,h=n.offsetHeight,u=A(this.options.offset),l=this._getAnchor(),c="top"===r?(i=a/2,h):"bottom"===r?(i=a/2,0):(i="center"===r?a/2:"right"===r?0:"left"===r?a:s.xthis.options.maxZoom||nthis.options.maxZoom||void 0!==this.options.minZoom&&oe.max.x)||!i.wrapLat&&(t.ye.max.y))return!1}if(!this.options.bounds)return!0;var n=this._tileCoordsToBounds(t);return N(this.options.bounds).overlaps(n)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),o=n.add(e);return[i.unproject(n,t.z),i.unproject(o,t.z)]},_tileCoordsToBounds:function(t){var i=this._tileCoordsToNwSe(t),e=new R(i[0],i[1]);return this.options.noWrap||(e=this._map.wrapLatLngBounds(e)),e},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var i=t.split(":"),e=new k(+i[0],+i[1]);return e.z=+i[2],e},_removeTile:function(t){var i=this._tiles[t];i&&(ri(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){ci(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=a,t.onmousemove=a,it&&this.options.opacity<1&&mi(t,this.options.opacity),ot&&!st&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,i){var e=this._getTilePos(t),n=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),p(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&M(p(this._tileReady,this,t,null,o)),vi(o,e),this._tiles[n]={el:o,coords:t,current:!0},i.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,i,e){i&&this.fire("tileerror",{error:i,tile:e,coords:t});var n=this._tileCoordsToKey(t);(e=this._tiles[n])&&(e.loaded=+new Date,this._map._fadeAnimated?(mi(e.el,0),z(this._fadeFrame),this._fadeFrame=M(this._updateOpacity,this)):(e.active=!0,this._pruneTiles()),i||(ci(e.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:e.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),it||!this._map._fadeAnimated?M(this._pruneTiles,this):setTimeout(p(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new k(this._wrapX?o(t.x,this._wrapX):t.x,this._wrapY?o(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new I(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});var sn=on.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,i){this._url=t,(i=c(this,i)).detectRetina&&zt&&0')}}catch(t){return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),_n={_initContainer:function(){this._container=si("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(hn.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=cn("shape");ci(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=cn("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[m(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;ri(i),t.removeInteractiveTarget(i),delete this._layers[m(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i=i||(t._stroke=cn("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=g(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e=e||(t._fill=cn("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){hi(t._container)},_bringToBack:function(t){ui(t._container)}},dn=Et?cn:J,pn=hn.extend({getEvents:function(){var t=hn.prototype.getEvents.call(this);return t.zoomstart=this._onZoomStart,t},_initContainer:function(){this._container=dn("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=dn("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){ri(this._container),Si(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_onZoomStart:function(){this._update()},_update:function(){var t,i,e;this._map._animatingZoom&&this._bounds||(hn.prototype._update.call(this),i=(t=this._bounds).getSize(),e=this._container,this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),vi(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update"))},_initPath:function(t){var i=t._path=dn("path");t.options.className&&ci(i,t.options.className),t.options.interactive&&ci(i,"leaflet-interactive"),this._updateStyle(t),this._layers[m(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){ri(t._path),t.removeInteractiveTarget(t._path),delete this._layers[m(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,e=t.options;i&&(e.stroke?(i.setAttribute("stroke",e.color),i.setAttribute("stroke-opacity",e.opacity),i.setAttribute("stroke-width",e.weight),i.setAttribute("stroke-linecap",e.lineCap),i.setAttribute("stroke-linejoin",e.lineJoin),e.dashArray?i.setAttribute("stroke-dasharray",e.dashArray):i.removeAttribute("stroke-dasharray"),e.dashOffset?i.setAttribute("stroke-dashoffset",e.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),e.fill?(i.setAttribute("fill",e.fillColor||e.color),i.setAttribute("fill-opacity",e.fillOpacity),i.setAttribute("fill-rule",e.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,$(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n="a"+e+","+(Math.max(Math.round(t._radiusY),1)||e)+" 0 1,0 ",o=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,o)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){hi(t._path)},_bringToBack:function(t){ui(t._path)}});function mn(t){return Zt||Et?new pn(t):null}Et&&pn.include(_n),Ki.include({getRenderer:function(t){var i=(i=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer)||(this._renderer=this._createRenderer());return this.hasLayer(i)||this.addLayer(i),i},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&ln(t)||mn(t)}});var fn=Re.extend({initialize:function(t,i){Re.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=N(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});pn.create=dn,pn.pointsToPath=$,Ne.geometryToLayer=De,Ne.coordsToLatLng=We,Ne.coordsToLatLngs=He,Ne.latLngToCoords=Fe,Ne.latLngsToCoords=Ue,Ne.getFeature=Ve,Ne.asFeature=qe,Ki.mergeOptions({boxZoom:!0});var gn=ie.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){zi(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){Si(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){ri(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),Xt(),xi(),this._startPoint=this._map.mouseEventToContainerPoint(t),zi(document,{contextmenu:Ni,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=si("div","leaflet-zoom-box",this._container),ci(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var i=new I(this._point,this._startPoint),e=i.getSize();vi(this._box,i.min),this._box.style.width=e.x+"px",this._box.style.height=e.y+"px"},_finish:function(){this._moved&&(ri(this._box),_i(this._container,"leaflet-crosshair")),Jt(),wi(),Si(document,{contextmenu:Ni,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){var i;1!==t.which&&1!==t.button||(this._finish(),this._moved&&(this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(p(this._resetState,this),0),i=new R(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point)),this._map.fitBounds(i).fire("boxzoomend",{boxZoomBounds:i})))},_onKeyDown:function(t){27===t.keyCode&&this._finish()}});Ki.addInitHook("addHandler","boxZoom",gn),Ki.mergeOptions({doubleClickZoom:!0});var vn=ie.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,o=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(o):i.setZoomAround(t.containerPoint,o)}});Ki.addInitHook("addHandler","doubleClickZoom",vn),Ki.mergeOptions({dragging:!0,inertia:!st,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0});var yn=ie.extend({addHooks:function(){var t;this._draggable||(t=this._map,this._draggable=new ae(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))),ci(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){_i(this._map._container,"leaflet-grab"),_i(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t,i=this._map;i._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity?(t=N(this._map.options.maxBounds),this._offsetLimit=O(this._map.latLngToContainerPoint(t.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(t.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))):this._offsetLimit=null,i.fire("movestart").fire("dragstart"),i.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){var i,e;this._map.options.inertia&&(i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos,this._positions.push(e),this._times.push(i),this._prunePositions(i)),this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;1i.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t))},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,s=(n+i+e)%t-i-e,r=Math.abs(o+e)i.getMaxZoom()&&1