[
  {
    "path": ".gitignore",
    "content": "## Maven stuff\ntarget/\npom.xml.tag\npom.xml.releaseBackup\npom.xml.versionsBackup\npom.xml.next\nrelease.properties\ndependency-reduced-pom.xml\nbuildNumber.properties\n.mvn/timing.properties\n\n# Exclude maven wrapper\n!/.mvn/wrapper/maven-wrapper.jar\n\n## Eclipse stuff\n.metadata\nbin/\ntmp/\n*.tmp\n*.bak\n*.swp\n*~.nib\nlocal.properties\n.settings/\n.loadpath\n.recommenders\n\n# Eclipse Core\n.project\n\n# External tool builders\n.externalToolBuilders/\n\n# Locally stored \"Eclipse launch configurations\"\n*.launch\n\n# PyDev specific (Python IDE for Eclipse)\n*.pydevproject\n\n# CDT-specific (C/C++ Development Tooling)\n.cproject\n\n# JDT-specific (Eclipse Java Development Tools)\n.classpath\n\n# Java annotation processor (APT)\n.factorypath\n\n# PDT-specific (PHP Development Tools)\n.buildpath\n\n# sbteclipse plugin\n.target\n\n# Tern plugin\n.tern-project\n\n# TeXlipse plugin\n.texlipse\n\n# STS (Spring Tool Suite)\n.springBeans\n\n# Code Recommenders\n.recommenders/\nfrontend/generated\n"
  },
  {
    "path": "README.md",
    "content": "# A Spring Boot example editing spatial data in relational database\n\n![Alt text](./screenshot.png?raw=true \"Screenshot\")\n\n## How To run?\n\nMake sure you have Docker installed and running (needed to create test DB) and a modern Java IDE that supports at least JDK 21. (see older versions of the example if you are tied to some legacy versions).\n\nEven if you don't want to run it, you probably want to first import the code to your favourite Java IDE (tested in IntelliJ last time) for easier exploring of the demo code. Then locate the TestApp class, and it's main method (src/main/test), run it! This will:\n\n * Use Docker to get a postgres with postgis extensions\n * Wire that to this Spring Boot app for development\n * Run the Vaadin UI in development mode\n\nAlternatively, if you have Maven installed, run from CLI:\n\n    mvn spring-boot:test-run\n\n## What it showcases\n\nThis is a small example app that shows how one can use:\n\n * [Spring Boot](http://projects.spring.io/spring-boot/) and [Spring Data](https://spring.io/projects/spring-data)\n * Latest [Hibernate](http://hibernate.org/orm/) with spatial features. At the application API, only standard JPA stuff (and Spring Data) is used.\n * ~~The example also uses [QueryDSL](http://www.querydsl.com) spatial query as an example. QueryDSL contain excellent support for spatial types.~~ QueryDSL example replaced with plain JPQL(with Hibernate spatial extensions) as the latest version is not compatible with latest JTS/Hibernate. See https://github.com/querydsl/querydsl/issues/2404. If you want to see the example of QueryDSL usage in this setup, check out a bit older version of the example.\n * Relational database, like PostGis (default, Postgres + extentiosn), H2GIS or MySQL, which supports basic spatial types. The example automatically launches Docker image with PostGis for the demo using TestContainers, if run via TestApp class in src/test/java/org/vaadin/example. Not that Hibernate might need tiny adjustments for other databases.\n * [Vaadin](https://vaadin.com/) and [MapLibreGL }> add-on](https://vaadin.com/directory/component/maplibregl--add-on) to build the UI layer. MapLibre add-on is a Vaadin wrapper for [MapLibre GL JS](https://github.com/maplibre/maplibre-gl-js) slippy map widget and [mapbox-gl-draw](https://github.com/mapbox/mapbox-gl-draw). Its Vaadin field implementations which make it dead simple to edit [JTS](https://locationtech.github.io/jts/) data types directly from the JPA entities.\n * As base layer for maps, crisp vector format [OpenStreetMap](https://www.openstreetmap.org/) data via [MapTiler](https://www.maptiler.com) is used, but naturally any common background map can be used.\n\n...to build a full-stack web app handling spatial data efficiently.\n\nAs the data is in an optimized form in the DB, it is possible to create efficient queries to the backend and e.g. only show features relevant to the current viewport of the map visualizing features or what ever you can with the spatial queries.\n\nEnjoy!\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>4.0.0</version>\n        <relativePath/> <!-- lookup parent from repository -->\n    </parent>\n    <groupId>org.vaadin</groupId>\n    <artifactId>spatial-spring-boot-app</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <name>spatial-spring-boot-app</name>\n    <description>Spatial example</description>\n    <properties>\n        <java.version>21</java.version>\n        <vaadin.version>25.0.0-rc2</vaadin.version>\n    </properties>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>com.vaadin</groupId>\n                <artifactId>vaadin-bom</artifactId>\n                <version>${vaadin.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <dependencies>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-jpa</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.hibernate.orm</groupId>\n            <artifactId>hibernate-spatial</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.vaadin</groupId>\n            <artifactId>vaadin-spring-boot-starter</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.vaadin</groupId>\n            <artifactId>vaadin-dev</artifactId>\n            <!-- Only a dev time dependency -->\n            <optional>true</optional>\n        </dependency>\n\n        <dependency>\n            <groupId>org.parttio</groupId>\n            <artifactId>maplibre</artifactId>\n            <version>2.0.0</version>\n        </dependency>\n\n        <dependency>\n            <groupId>in.virit</groupId>\n            <artifactId>viritin</artifactId>\n            <version>3.0.0</version>\n        </dependency>\n\n        <dependency>\n            <groupId>org.postgresql</groupId>\n            <artifactId>postgresql</artifactId>\n            <scope>runtime</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-testcontainers</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.testcontainers</groupId>\n            <artifactId>testcontainers-junit-jupiter</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.testcontainers</groupId>\n            <artifactId>testcontainers-postgresql</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-devtools</artifactId>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n            <plugin>\n                <groupId>com.vaadin</groupId>\n                <artifactId>vaadin-maven-plugin</artifactId>\n                <version>${vaadin.version}</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>build-frontend</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "src/main/java/org/vaadin/example/AbstractEntity.java",
    "content": "package org.vaadin.example;\n\nimport java.io.Serializable;\nimport java.util.Objects;\nimport jakarta.persistence.GeneratedValue;\nimport jakarta.persistence.GenerationType;\nimport jakarta.persistence.Id;\nimport jakarta.persistence.MappedSuperclass;\nimport jakarta.persistence.Version;\n\n/**\n *\n * @author Matti Tahvonen\n */\n@MappedSuperclass\npublic abstract class AbstractEntity implements Serializable, Cloneable {\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.AUTO)\n    private Long id;\n    \n    @Version\n    private int version;\n\n    public Long getId() {\n        return id;\n    }\n\n    protected void setId(Long id) {\n        this.id = id;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj) {\n            return true;\n        }\n        if(this.id == null) {\n            return false;\n        }\n\n        if (obj instanceof AbstractEntity && obj.getClass().equals(getClass())) {\n            return this.id.equals(((AbstractEntity) obj).id);\n        }\n\n        return false;\n    }\n\n    @Override\n    public int hashCode() {\n        int hash = 5;\n        hash = 43 * hash + Objects.hashCode(this.id);\n        return hash;\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/org/vaadin/example/AppShell.java",
    "content": "package org.vaadin.example;\n\nimport com.vaadin.flow.component.dependency.StyleSheet;\nimport com.vaadin.flow.component.page.AppShellConfigurator;\nimport com.vaadin.flow.theme.lumo.Lumo;\n\n@StyleSheet(Lumo.STYLESHEET)\npublic class AppShell implements AppShellConfigurator {\n}\n"
  },
  {
    "path": "src/main/java/org/vaadin/example/Application.java",
    "content": "package org.vaadin.example;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.Bean;\nimport org.vaadin.addons.maplibre.BaseMapConfigurer;\n\n/**\n * This would be the main app for deployment artifact.\n * For deployment, you need to configure DB.\n * For local testing & development, use TestApp that\n * launches PostGIS using TestContainers.\n */\n@SpringBootApplication\npublic class Application {\n\n    public static void main(String[] args) {\n        SpringApplication.run(Application.class, args);\n    }\n\n    // Configure default base map\n    @Bean\n    BaseMapConfigurer baseMapProvider() {\n\n        return map -> {\n            // NOTE, Create your own API key in maptiler! This is registered to work on localhost for the demo only\n            map.initStyle(\"https://api.maptiler.com/maps/streets/style.json?key=G5n7stvZjomhyaVYP0qU\");\n        };\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/vaadin/example/EventEditor.java",
    "content": "package org.vaadin.example;\n\n\nimport com.vaadin.flow.component.Component;\nimport com.vaadin.flow.component.UI;\nimport com.vaadin.flow.component.datepicker.DatePicker;\nimport com.vaadin.flow.component.textfield.TextField;\nimport com.vaadin.flow.router.Route;\nimport org.vaadin.addons.maplibre.LineStringField;\nimport org.vaadin.addons.maplibre.PointField;\nimport org.vaadin.firitin.components.orderedlayout.VHorizontalLayout;\nimport org.vaadin.firitin.components.orderedlayout.VVerticalLayout;\nimport org.vaadin.firitin.components.textfield.VTextField;\nimport org.vaadin.firitin.form.AbstractForm;\n\n@Route\npublic class EventEditor extends AbstractForm<SportEvent> {\n\n    private final SportEventService service;\n    private TextField title = new VTextField(\"Title\");\n    private DatePicker date = new DatePicker(\"Date\");\n    private PointField location = new PointField(\"Location\");\n    private LineStringField route = new LineStringField(\"Route\");\n\n    public EventEditor(SportEventService service) {\n        super(SportEvent.class);\n        this.service = service;\n        setSavedHandler(sportevent -> {\n            service.save(sportevent);\n            UI.getCurrent().navigate(MainView.class);\n        });\n\n        setResetHandler(sportevent -> {\n            UI.getCurrent().navigate(MainView.class);\n        });\n        getDeleteButton().setVisible(false);\n    }\n\n    @Override\n    protected Component createContent() {\n        getContent().setSizeFull();\n        location.setSizeFull();\n        route.setSizeFull();\n        return new VVerticalLayout()\n                .withComponent(new VHorizontalLayout(title, date))\n                .withExpanded(new VHorizontalLayout(location, route)\n                        .withSizeFull())\n                .withComponent(getToolbar())\n                .withFullHeight();\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/org/vaadin/example/MainView.java",
    "content": "package org.vaadin.example;\n\nimport com.vaadin.flow.component.UI;\nimport com.vaadin.flow.component.button.Button;\nimport com.vaadin.flow.component.icon.VaadinIcon;\nimport com.vaadin.flow.component.orderedlayout.HorizontalLayout;\nimport com.vaadin.flow.component.textfield.TextField;\nimport com.vaadin.flow.router.Route;\nimport in.virit.color.NamedColor;\nimport org.locationtech.jts.geom.Point;\nimport org.locationtech.jts.geom.Polygon;\nimport org.vaadin.addons.maplibre.DrawControl;\nimport org.vaadin.addons.maplibre.LinePaint;\nimport org.vaadin.addons.maplibre.MapLibre;\nimport org.vaadin.addons.maplibre.Marker;\nimport org.vaadin.firitin.components.RichText;\nimport org.vaadin.firitin.components.button.DeleteButton;\nimport org.vaadin.firitin.components.button.VButton;\nimport org.vaadin.firitin.components.grid.VGrid;\nimport org.vaadin.firitin.components.orderedlayout.VVerticalLayout;\nimport org.vaadin.firitin.components.textfield.VTextField;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author mstahv\n */\n@Route\npublic class MainView extends VVerticalLayout {\n\n    private final SportEventService service;\n    TextField filter = new VTextField()\n            .withPlaceholder(\"Filter by name...\");\n\n    private Map<SportEvent,Marker> eventToMarker = new HashMap<>();\n\n    private RichText infoText = new RichText().withMarkDown(\n            \"###V-Leaflet example\\n\\n\"\n                    + \"This is small example app to demonstrate how to add simple GIS \"\n                    + \"features to your Spring Boot Vaadin app. \"\n                    + \"[Check out the sources!](https://github.com/mstahv/spring-boot-spatial-example)\");\n    private VGrid<SportEvent> table = new VGrid<>(SportEvent.class);\n    private Button addNew = new VButton(\"New event...\")\n            .withIcon(VaadinIcon.PLUS.create())\n            .withClickListener(e -> {\n                UI.getCurrent().navigate(EventEditor.class)\n                        .get().setEntity(new SportEvent());\n            });\n\n    // Note, the base map here and in editors could be defined\n    // here, but are instead defined application wide in Application class\n    private MapLibre map = new MapLibre();\n\n    public MainView(SportEventService service) {\n        this.service = service;\n        service.ensureTestData();\n        var drawControl = new DrawControl(map);\n        drawControl.addGeometryChangeListener(e -> {\n            Polygon p = (Polygon) e.getGeom().getGeometryN(0);\n            loadEventsWithinBounds(p);\n            drawControl.clear();\n        });\n        add(new HorizontalLayout(\n                addNew,\n                new VButton(\"Draw area to list events\", e -> {\n                    drawControl.setMode(DrawControl.DrawMode.DRAW_POLYGON);\n                }),\n                new VButton(\"List events within viewport\", e -> {\n                    loadEventsInViewport();\n                }),\n                filter\n        ));\n        withExpanded(map, table);\n\n        filter.addValueChangeListener(e -> {\n            loadEventsByNameFilter(e.getValue());\n        });\n\n        table.addComponentColumn(sportEvent ->\n                new HorizontalLayout(\n                        new VButton(VaadinIcon.EDIT.create(), e-> {\n                            UI.getCurrent().navigate(EventEditor.class).get()\n                                    .setEntity(sportEvent);\n                        }),\n                        new DeleteButton(() -> {\n                            delete(sportEvent);\n                        })\n                ));\n\n\n\n        table.asSingleSelect().addValueChangeListener(e -> {\n            SportEvent sportEvent = e.getValue();\n            if(e.isFromClient() && sportEvent != null) {\n                // open marker popup and center the map to event\n                Marker marker = eventToMarker.get(sportEvent);\n                marker.openPopup();\n                map.flyTo(marker.getGeometry(), 10);\n            }\n        });\n\n        loadEventsByNameFilter(\"\");\n        map.fitToContent();\n\n    }\n\n    public void delete(SportEvent event) {\n        service.delete(event);\n        loadEventsInViewport();\n    }\n\n    private void loadEventsByNameFilter(String value) {\n        List<SportEvent> events = service.filterByTitle(value);\n        map.fitToContent();\n        setEvents(events);\n    }\n\n    private void loadEventsInViewport() {\n        map.getViewPort().thenAccept(vp -> {\n            Polygon bounds = vp.getBounds();\n            loadEventsWithinBounds(bounds);\n        });\n    }\n\n    private void loadEventsWithinBounds(Polygon bounds) {\n        setEvents(service.filterByBounds(bounds));\n    }\n\n    private void setEvents(List<SportEvent> events) {\n        /* Populate table... */\n        table.setItems(events);\n        /* ... and map */\n        map.removeAll();\n        eventToMarker.clear();\n        for (final SportEvent sportEvent : events) {\n            /*\n             * Adds geometries to the map. Note that this method adds a separate\n             * layer per geometry and is thus not very optimised. For better\n             * performance with a large number of geometries, combine layers\n             * or load features as vector tiles (~ lazy load only the visible portion)\n             * if there is a ton of those.\n             */\n\n            if(sportEvent.getLocation() != null){\n                Point p = sportEvent.getLocation();\n                Marker marker = map.addMarker(p)\n                        .withPopup(sportEvent.getTitle());\n                eventToMarker.put(sportEvent,marker);\n                marker.addClickListener( () -> {\n                    // focus in Table\n                    table.asSingleSelect().setValue(sportEvent);\n                });\n            }\n            if(sportEvent.getRoute() != null) {\n                map.addLineLayer(sportEvent.getRoute(), new LinePaint(NamedColor.BLUE, 3.0));\n                // TODO add click listener also for lines\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/vaadin/example/SportEvent.java",
    "content": "package org.vaadin.example;\n\nimport jakarta.persistence.Column;\nimport jakarta.persistence.Entity;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport org.locationtech.jts.geom.LineString;\nimport org.locationtech.jts.geom.Point;\n\nimport java.time.LocalDate;\n\n@Entity\npublic class SportEvent extends AbstractEntity {\n\n    @NotEmpty\n    private String title;\n\n    @Column(columnDefinition = \"DATE\")\n    private LocalDate date;\n\n    @NotNull\n    @Column(columnDefinition = \"geometry\")\n    private Point location;\n\n    @Column(columnDefinition = \"geometry\")\n    private LineString route;\n\n    public SportEvent() {\n    }\n\n    public LocalDate getDate() {\n        return date;\n    }\n\n    public void setDate(LocalDate date) {\n        this.date = date;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    public LineString getRoute() {\n        return route;\n    }\n\n    public void setRoute(LineString route) {\n        this.route = route;\n    }\n\n    public Point getLocation() {\n        return location;\n    }\n\n    public void setLocation(Point location) {\n        this.location = location;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/vaadin/example/SportEventRepository.java",
    "content": "package org.vaadin.example;\n\nimport java.util.List;\nimport org.locationtech.jts.geom.Geometry;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.Query;\nimport org.springframework.data.repository.query.Param;\n\n/**\n * @author mstahv\n */\npublic interface SportEventRepository extends JpaRepository<SportEvent, Long> {\n\n    /**\n     * Example method of a GIS query. This uses Hibernate spatial extensions, so\n     * it does not work with other JPA implementations.\n     *\n     * @param bounds the geometry\n     * @return SpatialEvents inside given geometry and with given filter for the title\n     */\n    @Query(value = \"SELECT se FROM SportEvent se WHERE within(se.location, :bounds) = true\")\n    public List<SportEvent> findAllWithin(@Param(\"bounds\") Geometry bounds);\n\n    public List<SportEvent> findByTitleContainingIgnoreCase(String title);\n\n}\n"
  },
  {
    "path": "src/main/java/org/vaadin/example/SportEventService.java",
    "content": "package org.vaadin.example;\n\nimport org.locationtech.jts.geom.GeometryFactory;\nimport org.locationtech.jts.geom.LineString;\nimport org.locationtech.jts.geom.Point;\nimport org.locationtech.jts.geom.Polygon;\nimport org.locationtech.jts.geom.PrecisionModel;\nimport org.locationtech.jts.io.ParseException;\nimport org.locationtech.jts.io.WKTReader;\nimport org.springframework.stereotype.Service;\n\nimport java.time.LocalDate;\nimport java.util.List;\n\n@Service\npublic class SportEventService {\n\n    private final SportEventRepository repo;\n\n    public SportEventService(SportEventRepository repository) {\n        this.repo = repository;\n    }\n\n    public void ensureTestData() {\n        if (repo.count() == 0) {\n            GeometryFactory factory = new GeometryFactory();\n\n            WKTReader wktReader = new WKTReader(new GeometryFactory(new PrecisionModel(), 4326));\n\n            try {\n                SportEvent theEvent = new SportEvent();\n                theEvent.setTitle(\"Lutakon kierros\");\n                theEvent.setDate(LocalDate.now());\n                theEvent.setLocation((Point) wktReader.read(\"POINT (25.77554278820253 62.2272018311204)\"));\n                theEvent.setRoute((LineString) wktReader.read(\"LINESTRING (25.77554278820253 62.2272018311204, 25.779524086129754 62.22702791012401, 25.787113435301052 62.22627424088853, 25.800301484682706 62.22586841119525, 25.802540964765228 62.2265061411197, 25.813587285546873 62.23417854401356, 25.816200012311214 62.23904679898311, 25.816448843431488 62.240843218395554, 25.80587352081554 62.24101705975687, 25.79380521148309 62.24345073358492, 25.788330926834163 62.24374044357751, 25.779497422061553 62.248201625968164, 25.77277898180992 62.251503634515785, 25.76394547703589 62.249070610653376, 25.754987556701792 62.244319855211785, 25.753743401100365 62.24240775456448, 25.749388856494107 62.23927860104487, 25.760710672470935 62.23632299124034, 25.76842443720369 62.231686157027355, 25.771270557328904 62.229011191767825, 25.77500302413449 62.227387987489095)\"));\n                repo.save(theEvent);\n\n                SportEvent eventWithPath = new SportEvent();\n                eventWithPath.setRoute((LineString) wktReader.read(\"LINESTRING (22.69504539358053 60.41742475722279, 22.697454647646992 60.41690574465318, 22.698768786227845 60.41649485382865, 22.700389557144803 60.41675436442708, 22.701046626435698 60.41671111280451, 22.702273155778926 60.415456790730445, 22.70354348974095 60.41446194917475, 22.70823058401524 60.41346707719282, 22.708537216350322 60.41281823124683, 22.710026573409664 60.41203959902907, 22.711121688894224 60.41048227868464, 22.71291767828862 60.41095813447669, 22.71414420763176 60.41193145419163, 22.715589760071282 60.411888196155985, 22.71686009403328 60.412104485759016, 22.71830564647621 60.41266683200189, 22.717604772566403 60.413164284033, 22.715940197029653 60.41283985965799, 22.713881379918092 60.412147743511554, 22.708975262547455 60.41126094817949, 22.70849341173391 60.411087912125595, 22.708186779398915 60.41242891747186, 22.708756239451105 60.412904744791774, 22.710289401129273 60.41299125810218, 22.71195397666608 60.413553589012, 22.70595274380986 60.41454845834818, 22.70595274380986 60.41521889660916, 22.70463860522895 60.41640834984031, 22.703806317460618 60.41710037534173, 22.701440868013975 60.417338255707534, 22.70043336176809 60.41720850299629, 22.69949230755762 60.417445646906515, 22.698791433647784 60.41798627584606, 22.697915341259574 60.41869989228667, 22.696075547246295 60.41872151678288, 22.694410971709488 60.418462021879435, 22.694104339373496 60.4180727756424, 22.694104339373496 60.41764027436014, 22.695155650239172 60.417424021562056)\"));\n                eventWithPath.setLocation((Point) wktReader.read(\"POINT (22.694516300414023 60.4174531804353)\"));\n                eventWithPath.setDate(LocalDate.now());\n                eventWithPath.setTitle(\"MTB cup 1/10\");\n                repo.save(eventWithPath);\n\n            } catch (ParseException e) {\n                throw new RuntimeException(e);\n            }\n        }\n\n    }\n\n    public void save(SportEvent sportevent) {\n        repo.save(sportevent);\n    }\n\n    public void delete(SportEvent event) {\n        repo.deleteById(event.getId());\n    }\n\n    public List<SportEvent> filterByTitle(String value) {\n        return repo.findByTitleContainingIgnoreCase(value);\n    }\n\n    public List<SportEvent> filterByBounds(Polygon bounds) {\n        return repo.findAllWithin(bounds);\n    }\n}\n"
  },
  {
    "path": "src/main/resources/application.properties",
    "content": "#spring.datasource.url=jdbc:mysql://localhost/spatialdemo\n#spring.datasource.username=root\n#spring.datasource.password=\nspring.jpa.hibernate.ddl-auto=create-drop\n#spring.jpa.properties.hibernate.dialect=org.hibernate.spatial.dialect.h2geodb.GeoDBDialect\n#spring.jpa.properties.hibernate.dialect=org.hibernate.spatial.dialect.mysql.MySQL56InnoDBSpatialDialect\n\n# basic log level for all messages\nlogging.level.org.hibernate=info\n# SQL statements and parameters\nlogging.level.org.hibernate.SQL=debug\nlogging.level.org.hibernate.orm.jdbc.bind=trace\n# Statistics and slow queries\nlogging.level.org.hibernate.stat=debug\nlogging.level.org.hibernate.SQL_SLOW=info\n# 2nd Level Cache\nlogging.level.org.hibernate.cache=debug\n"
  },
  {
    "path": "src/main/resources/schema-geodb.sql",
    "content": "CREATE ALIAS InitGeoDB for \"geodb.GeoDB.InitGeoDB\";\nCALL InitGeoDB();"
  },
  {
    "path": "src/test/java/org/vaadin/example/DatabaseTestContainerConfiguration.java",
    "content": "package org.vaadin.example;\n\nimport org.springframework.boot.devtools.restart.RestartScope;\nimport org.springframework.boot.test.context.TestConfiguration;\nimport org.springframework.boot.testcontainers.service.connection.ServiceConnection;\nimport org.springframework.context.annotation.Bean;\nimport org.testcontainers.containers.PostgreSQLContainer;\nimport org.testcontainers.utility.DockerImageName;\n\n@TestConfiguration(proxyBeanMethods = false)\nclass DatabaseTestContainerConfiguration {\n\n    @Bean\n    @ServiceConnection\n    @RestartScope\n    PostgreSQLContainer postgis() {\n        PostgreSQLContainer<?> postgis = new PostgreSQLContainer<>(\n                DockerImageName.parse(\"postgis/postgis:16-3.4-alpine\").asCompatibleSubstituteFor(\"postgres\")\n        );\n        return postgis;\n    }\n}"
  },
  {
    "path": "src/test/java/org/vaadin/example/SpatialSpringBootAppApplicationTests.java",
    "content": "package org.vaadin.example;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.test.context.junit4.SpringRunner;\n\n@Import(DatabaseTestContainerConfiguration.class)\n@SpringBootTest\nclass DemoApplicationTests {\n\n    @Test\n    void contextLoads() {\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/vaadin/example/TestApp.java",
    "content": "package org.vaadin.example;\n\nimport org.springframework.boot.SpringApplication;\n\npublic class TestApp {\n\n    public static void main(String[] args) {\n        SpringApplication.from(Application::main)\n                .with(DatabaseTestContainerConfiguration.class)\n                .run(args);\n    }\n\n}"
  }
]