diff --git a/README.rst b/README.rst index df9e4ed..76d733d 100644 --- a/README.rst +++ b/README.rst @@ -287,6 +287,16 @@ style raster colormap --raster raster --values "10=red,50=blue,100=wheat,250=whi style raster palette colormap --min 1 --max 50 --palette MutedTerrain --number 20 --file terrain.sld +style repository save --type h2 --options file=styles_county.db --layerName roads --styleName roads --styleFile examples/roads.sld + +style repository get --type nested-directory --options file=examples/county_styles --layerName roads --styleName roads + +style repository list --type directory --options file=examples/styles + +style repository delete --type directory --options file=examples/styles --layerName parcels --styleName parcels + +style repository copy --inputType sqlite --inputOptions file=examples/my-styles.db --outputType h2 --outputOptions file=examples/h2-styles.db + map --- map open --name state_map diff --git a/src/main/docs/style.adoc b/src/main/docs/style.adoc index ce159cf..f6f6571 100644 --- a/src/main/docs/style.adoc +++ b/src/main/docs/style.adoc @@ -338,3 +338,126 @@ include::output/style_raster_palette_colormap.sld[] image::style_raster_palette_colormap.png[] +=== Style Repository Save + +include::commands/style_repository_save_description.txt[] + +include::output/style_repository_save_1_command.txt[] + +include::commands/style_repository_save.txt[] + +include::output/style_repository_save_0_command.txt[] +include::output/style_repository_save_0_result.txt[] + +include::output/style_repository_save_1_command.txt[] +include::output/style_repository_save_1_result.txt[] + +=== Style Repository List + +include::commands/style_repository_list_description.txt[] + +include::output/style_repository_list_4_command.txt[] + +include::commands/style_repository_list.txt[] + +include::output/style_repository_list_0_command.txt[] +include::output/style_repository_list_0_result.txt[] + +include::output/style_repository_list_1_command.txt[] +include::output/style_repository_list_1_result.txt[] + +include::output/style_repository_list_2_command.txt[] +include::output/style_repository_list_2_result.txt[] + +include::output/style_repository_list_3_command.txt[] +include::output/style_repository_list_3_result.txt[] + +include::output/style_repository_list_4_command.txt[] +include::output/style_repository_list_4_result.txt[] + +include::output/style_repository_list_5_command.txt[] +include::output/style_repository_list_5_result.txt[] + +=== Style Repository Delete + +include::commands/style_repository_delete_description.txt[] + +include::output/style_repository_delete_5_command.txt[] + +include::commands/style_repository_delete.txt[] + +include::output/style_repository_delete_0_command.txt[] +include::output/style_repository_delete_0_result.txt[] + +include::output/style_repository_delete_1_command.txt[] +include::output/style_repository_delete_1_result.txt[] + +include::output/style_repository_delete_2_command.txt[] +include::output/style_repository_delete_2_result.txt[] + +include::output/style_repository_delete_3_command.txt[] +include::output/style_repository_delete_3_result.txt[] + +include::output/style_repository_delete_4_command.txt[] +include::output/style_repository_delete_4_result.txt[] + +include::output/style_repository_delete_5_command.txt[] +include::output/style_repository_delete_5_result.txt[] + +include::output/style_repository_delete_6_command.txt[] +include::output/style_repository_delete_6_result.txt[] + +=== Style Repository Get + +include::commands/style_repository_get_description.txt[] + +include::output/style_repository_get_4_command.txt[] + +include::commands/style_repository_get.txt[] + +include::output/style_repository_get_0_command.txt[] +include::output/style_repository_get_0_result.txt[] + +include::output/style_repository_get_1_command.txt[] +include::output/style_repository_get_1_result.txt[] + +include::output/style_repository_get_2_command.txt[] +include::output/style_repository_get_2_result.txt[] + +include::output/style_repository_get_3_command.txt[] +include::output/style_repository_get_3_result.txt[] + +include::output/style_repository_get_4_command.txt[] +include::output/style_repository_get_4_result.txt[] + +include::output/style_repository_get_5_command.txt[] +include::output/style_repository_get_5_result.txt[] + +=== Style Repository Copy + +include::commands/style_repository_copy_description.txt[] + +include::output/style_repository_copy_5_command.txt[] + +include::commands/style_repository_copy.txt[] + +include::output/style_repository_copy_0_command.txt[] +include::output/style_repository_copy_0_result.txt[] + +include::output/style_repository_copy_1_command.txt[] +include::output/style_repository_copy_1_result.txt[] + +include::output/style_repository_copy_2_command.txt[] +include::output/style_repository_copy_2_result.txt[] + +include::output/style_repository_copy_3_command.txt[] +include::output/style_repository_copy_3_result.txt[] + +include::output/style_repository_copy_4_command.txt[] +include::output/style_repository_copy_4_result.txt[] + +include::output/style_repository_copy_5_command.txt[] +include::output/style_repository_copy_5_result.txt[] + +include::output/style_repository_copy_6_command.txt[] +include::output/style_repository_copy_6_result.txt[] diff --git a/src/main/groovy/org/geoshell/style/StyleCommands.groovy b/src/main/groovy/org/geoshell/style/StyleCommands.groovy index ac3062f..ff20bf2 100644 --- a/src/main/groovy/org/geoshell/style/StyleCommands.groovy +++ b/src/main/groovy/org/geoshell/style/StyleCommands.groovy @@ -7,6 +7,7 @@ import geoscript.style.ColorMap import geoscript.style.Gradient import geoscript.style.RasterSymbolizer import geoscript.style.Style +import geoscript.style.StyleRepository import geoscript.style.Symbolizer import geoscript.style.UniqueValues import geoscript.style.io.SLDWriter @@ -218,4 +219,105 @@ class StyleCommands implements CommandMarker { "Colormap Palette Raster Style written to ${file}!" } + @CliCommand(value = "style repository save", help = "Save a style to a style repository") + String saveStyleToStyleRepository( + @CliOption(key = "type", mandatory = true, help = "The type of style repository (directory, nested-directory, h2, sqlite, postgres)") + String type, + @CliOption(key = "options", mandatory = true, help = "The style repository options") + String params, + @CliOption(key = "layerName", mandatory = true, help = "The layer name") + String layerName, + @CliOption(key = "styleName", mandatory = true, help = "The style name") + String styleName, + @CliOption(key = "styleFile", mandatory = true, help = "The style file (sld or css)") + File styleFile + ) { + StyleRepository styleRepository = StyleRepositoryFactory.getStyleRepository(type, StyleRepositoryFactory.getParameters(params)) + styleRepository.save(layerName, styleName, styleFile.text) + "Style ${styleName} for Layer ${layerName} saved to ${type}" + } + + @CliCommand(value = "style repository delete", help = "Delete a style from a style repository") + String deleteStyleFromStyleRepository( + @CliOption(key = "type", mandatory = true, help = "The type of style repository (directory, nested-directory, h2, sqlite, postgres)") + String type, + @CliOption(key = "options", mandatory = true, help = "The style repository options") + String params, + @CliOption(key = "layerName", mandatory = true, help = "The layer name") + String layerName, + @CliOption(key = "styleName", mandatory = true, help = "The style name") + String styleName + ) { + StyleRepository styleRepository = StyleRepositoryFactory.getStyleRepository(type, StyleRepositoryFactory.getParameters(params)) + styleRepository.delete(layerName, styleName) + "Style ${styleName} for Layer ${layerName} deleted from ${type}" + } + + @CliCommand(value = "style repository get", help = "Get a style from a style repository") + String getStyleFromStyleRepository( + @CliOption(key = "type", mandatory = true, help = "The type of style repository (directory, nested-directory, h2, sqlite, postgres)") + String type, + @CliOption(key = "options", mandatory = true, help = "The style repository options") + String params, + @CliOption(key = "layerName", mandatory = true, help = "The layer name") + String layerName, + @CliOption(key = "styleName", mandatory = true, help = "The style name") + String styleName, + @CliOption(key = "styleFile", mandatory = false, help = "The style file (sld or css)") + File styleFile + ) { + StyleRepository styleRepository = StyleRepositoryFactory.getStyleRepository(type, StyleRepositoryFactory.getParameters(params)) + String style = styleRepository.getForLayer(layerName, styleName) + if (styleFile) { + styleFile.text = style + "Style ${styleName} for Layer ${layerName} saved to ${styleFile.name}" + } else { + style + } + } + + @CliCommand(value = "style repository list", help = "List styles in a style repository") + String listStylesInStyleRepository( + @CliOption(key = "type", mandatory = true, help = "The type of style repository (directory, nested-directory, h2, sqlite, postgres)") + String type, + @CliOption(key = "options", mandatory = true, help = "The style repository options") + String params, + @CliOption(key = "layerName", mandatory = false, help = "The layer name") + String layerName + ) { + StyleRepository styleRepository = StyleRepositoryFactory.getStyleRepository(type, StyleRepositoryFactory.getParameters(params)) + List> styles = [] + if (layerName) { + styles.addAll(styleRepository.getForLayer(layerName)) + } else { + styles.addAll(styleRepository.getAll()) + } + String NEW_LINE = System.getProperty("line.separator") + StringBuilder str = new StringBuilder() + styles.each { Map style -> + str.append(style.layerName + " " + style.styleName).append(NEW_LINE) + } + str.toString() + } + + @CliCommand(value = "style repository copy", help = "Copy styles from one repository to another") + String copyStylesInStyleRepository( + @CliOption(key = "inputType", mandatory = true, help = "The type of style repository (directory, nested-directory, h2, sqlite, postgres)") + String inputType, + @CliOption(key = "inputOptions", mandatory = true, help = "The style repository options") + String inputParams, + @CliOption(key = "outputType", mandatory = true, help = "The type of style repository (directory, nested-directory, h2, sqlite, postgres)") + String outputType, + @CliOption(key = "outputOptions", mandatory = true, help = "The style repository options") + String outputParams + ) { + StyleRepository inputStyleRepository = StyleRepositoryFactory.getStyleRepository(inputType, StyleRepositoryFactory.getParameters(inputParams)) + StyleRepository outputStyleRepository = StyleRepositoryFactory.getStyleRepository(outputType, StyleRepositoryFactory.getParameters(outputParams)) + List> styles = inputStyleRepository.getAll() + styles.each {Map style -> + outputStyleRepository.save(style.layerName, style.styleName, style.style) + } + "Copy styles from ${inputType} to ${outputType}" + } + } diff --git a/src/main/groovy/org/geoshell/style/StyleRepositoryFactory.groovy b/src/main/groovy/org/geoshell/style/StyleRepositoryFactory.groovy new file mode 100644 index 0000000..a2e849f --- /dev/null +++ b/src/main/groovy/org/geoshell/style/StyleRepositoryFactory.groovy @@ -0,0 +1,67 @@ +package org.geoshell.style + +import geoscript.style.DatabaseStyleRepository +import geoscript.style.DirectoryStyleRepository +import geoscript.style.NestedDirectoryStyleRepository +import geoscript.style.StyleRepository +import geoscript.workspace.H2 +import groovy.sql.Sql +import org.geotools.util.URLs + +class StyleRepositoryFactory { + + static StyleRepository getStyleRepository(String type, Map options) { + if (type.equalsIgnoreCase("directory")) { + new DirectoryStyleRepository(getFile(options.file)) + } else if (type.equalsIgnoreCase("nested-directory")) { + new NestedDirectoryStyleRepository(getFile(options.file)) + } else if (type.equalsIgnoreCase("sqlite")) { + File file = getFile(options.file) + Sql sql = Sql.newInstance("jdbc:sqlite:${file.absolutePath}", "org.sqlite.JDBC") + DatabaseStyleRepository.forSqlite(sql) + } else if (type.equalsIgnoreCase("h2")) { + File file = getFile(options.file) + H2 h2 = new H2(file.name, file) + Sql sql = h2.sql + DatabaseStyleRepository.forH2(sql) + } else if (type.equalsIgnoreCase("postgres")) { + String server = options.server ?: 'localhost' + String database = options.database + String port = options.port ?: '5432' + String userName = options.userName + String password = options.password + Sql sql = Sql.withInstance("jdbc:postgres://${server}:${port}/${database}", userName, password, "org.postgresql.Driver") + DatabaseStyleRepository.forPostgres(sql) + } + } + + static Map getParameters(String str) { + Map params = [:] + str.split("[ ]+(?=([^\']*\'[^\']*\')*[^\']*\$)").each { + def parts = it.split("=") + if (parts.size() > 1) { + def key = parts[0].trim() + if ((key.startsWith("'") && key.endsWith("'")) || + (key.startsWith("\"") && key.endsWith("\""))) { + key = key.substring(1, key.length() - 1) + } + def value = parts[1].trim() + if ((value.startsWith("'") && value.endsWith("'")) || + (value.startsWith("\"") && value.endsWith("\""))) { + value = value.substring(1, value.length() - 1) + } + params.put(key, value) + } + } + params + } + + private static File getFile(Object file) { + if (file instanceof File) { + file + } else { + new File(file.toString()) + } + } + +} diff --git a/src/test/groovy/org/geoshell/docs/StyleDocTest.groovy b/src/test/groovy/org/geoshell/docs/StyleDocTest.groovy index 4c3736f..d855fdd 100644 --- a/src/test/groovy/org/geoshell/docs/StyleDocTest.groovy +++ b/src/test/groovy/org/geoshell/docs/StyleDocTest.groovy @@ -133,7 +133,7 @@ class StyleDocTest extends AbstractDocTest { } @Test - void polygon() { + void rasterPaletteColorMap() { run("style_raster_palette_colormap", [ "format open --name high --input src/test/resources/high.tif", "raster open --format high --raster high --name high", @@ -148,4 +148,67 @@ class StyleDocTest extends AbstractDocTest { copyFile(new File("examples/style_raster_palette_colormap.sld"), new File("src/main/docs/output")) } + @Test + void saveStyleToStyleRepository() { + run("style_repository_save", [ + "style create --params \"stroke=black stroke-width=0.25 fill=wheat\" --file examples/fields.sld", + "style repository save --type sqlite --options file=target/styles.db --layerName fields --styleName fields --styleFile examples/fields.sld" + ]) + } + + @Test + void listStylesInStyleRepository() { + run("style_repository_list", [ + "style create --params \"stroke=black stroke-width=1.0\" --file examples/roads.sld", + "style create --params \"stroke=red stroke-width=0.50\" --file examples/parcels.sld", + "style repository save --type h2 --options file=target/styles_county.db --layerName roads --styleName roads --styleFile examples/roads.sld", + "style repository save --type h2 --options file=target/styles_county.db --layerName parcels --styleName parcels --styleFile examples/parcels.sld", + "style repository list --type h2 --options file=target/styles_county.db", + "style repository list --type h2 --options file=target/styles_county.db --layerName roads" + ]) + } + + @Test + void deleteStylesFromStyleRepository() { + File styleDirectory = new File("examples/styles") + styleDirectory.mkdir() + run("style_repository_delete", [ + "style create --params \"stroke=black stroke-width=1.0\" --file examples/roads.sld", + "style create --params \"stroke=red stroke-width=0.50\" --file examples/parcels.sld", + "style repository save --type directory --options file=examples/styles --layerName roads --styleName roads --styleFile examples/roads.sld", + "style repository save --type directory --options file=examples/styles --layerName parcels --styleName parcels --styleFile examples/parcels.sld", + "style repository list --type directory --options file=examples/styles", + "style repository delete --type directory --options file=examples/styles --layerName parcels --styleName parcels", + "style repository list --type directory --options file=examples/styles" + ]) + } + + @Test + void getStyleFromStyleRepository() { + File styleDirectory = new File("examples/county_styles") + styleDirectory.mkdir() + run("style_repository_get", [ + "style create --params \"stroke=black stroke-width=1.0\" --file examples/roads.sld", + "style create --params \"stroke=red stroke-width=0.50\" --file examples/parcels.sld", + "style repository save --type nested-directory --options file=examples/county_styles --layerName roads --styleName roads --styleFile examples/roads.sld", + "style repository save --type nested-directory --options file=examples/county_styles --layerName parcels --styleName parcels --styleFile examples/parcels.sld", + "style repository get --type nested-directory --options file=examples/county_styles --layerName roads --styleName roads", + "style repository get --type nested-directory --options file=examples/county_styles --layerName parcels --styleName parcels --styleFile examples/roads_simple.sld", + ]) + } + + @Test + void copyStyleRepository() { + + run("style_repository_copy", [ + "style create --params \"stroke=black stroke-width=1.0\" --file examples/roads.sld", + "style create --params \"stroke=red stroke-width=0.50\" --file examples/parcels.sld", + "style repository save --type sqlite --options file=target/my-styles.db --layerName roads --styleName roads --styleFile examples/roads.sld", + "style repository save --type sqlite --options file=target/my-styles.db --layerName parcels --styleName parcels --styleFile examples/parcels.sld", + "style repository list --type sqlite --options file=target/my-styles.db", + "style repository copy --inputType sqlite --inputOptions file=target/my-styles.db --outputType h2 --outputOptions file=target/h2-styles.db", + "style repository list --type h2 --options file=target/h2-styles.db" + ]) + } + } diff --git a/src/test/groovy/org/geoshell/style/StyleCommandsTest.groovy b/src/test/groovy/org/geoshell/style/StyleCommandsTest.groovy index 62bae5d..5df7402 100644 --- a/src/test/groovy/org/geoshell/style/StyleCommandsTest.groovy +++ b/src/test/groovy/org/geoshell/style/StyleCommandsTest.groovy @@ -144,4 +144,79 @@ Aa2=#94474b""" styleText.contains("") && styleText.contains("