diff --git a/src/main/kotlin/io/spring/messenger/config/DatabaseConfig.kt b/src/main/kotlin/io/spring/messenger/config/DatabaseConfig.kt index d3eb4a2..65eb7a3 100644 --- a/src/main/kotlin/io/spring/messenger/config/DatabaseConfig.kt +++ b/src/main/kotlin/io/spring/messenger/config/DatabaseConfig.kt @@ -24,22 +24,21 @@ open class DatabaseConfig { @Bean open fun init(userRepository: UserRepository, messageRepository: MessageRepository) = CommandLineRunner { - val jbauer = User("jbauer", "Jack", "Bauer") - val cobrian = User("cobrian", "Chloe", "O'Brian") - val kbauer = User("kbauer", "Kim", "Bauer") - val dpalmer = User("dpalmer", "David", "Palmer") - val mdessler = User("mdessler", "Michelle", "Dessler") + val swhite = User("swhite", "Skyler", "White") + val jpinkman = User("jpinkman", "Jesse", "Pinkman") + val walter = User("wwhite", "Walter", "White") + val sgoodman = User("sgoodman", "Saul", "Goodman") + if(userRepository.count() == 0L) { - userRepository.create(jbauer) - userRepository.create(cobrian) - userRepository.create(kbauer) - userRepository.create(dpalmer) - userRepository.create(mdessler) + userRepository.create(swhite) + userRepository.create(jpinkman) + userRepository.create(walter) + userRepository.create(sgoodman) } if(messageRepository.count() == 0L) { - messageRepository.create(Message("This is a test!", jbauer)) + messageRepository.create(Message("This is a test!", swhite.userName)) } } diff --git a/src/main/kotlin/io/spring/messenger/domain/Message.kt b/src/main/kotlin/io/spring/messenger/domain/Message.kt index e3ac766..a116188 100644 --- a/src/main/kotlin/io/spring/messenger/domain/Message.kt +++ b/src/main/kotlin/io/spring/messenger/domain/Message.kt @@ -5,7 +5,7 @@ import org.springframework.data.annotation.Id data class Message( var content: String, - var author: User, + var author: String, var location: Point? = null, @Id var id: Long? = null ) \ No newline at end of file diff --git a/src/main/kotlin/io/spring/messenger/domain/MessageWithAuthor.kt b/src/main/kotlin/io/spring/messenger/domain/MessageWithAuthor.kt new file mode 100644 index 0000000..e797bdf --- /dev/null +++ b/src/main/kotlin/io/spring/messenger/domain/MessageWithAuthor.kt @@ -0,0 +1,11 @@ +package io.spring.messenger.domain + +import org.postgis.Point +import org.springframework.data.annotation.Id + +data class MessageWithAuthor( + var content: String, + var author: User, + var location: Point? = null, + @Id var id: Long? = null +) \ No newline at end of file diff --git a/src/main/kotlin/io/spring/messenger/repository/MessageRepository.kt b/src/main/kotlin/io/spring/messenger/repository/MessageRepository.kt index 78356e9..a738d9f 100644 --- a/src/main/kotlin/io/spring/messenger/repository/MessageRepository.kt +++ b/src/main/kotlin/io/spring/messenger/repository/MessageRepository.kt @@ -9,11 +9,9 @@ import org.postgis.PGgeometry import org.postgis.Point import org.springframework.jdbc.core.RowMapper import org.springframework.stereotype.Repository -import java.sql.ResultSet @Repository -open class MessageRepository : JdbcRepository(MessageRowMapper(), MessageRowUnmapper(), - TableDescription("messages", "messages JOIN users ON messages.author = users.user_name", "id")) { +open class MessageRepository : JdbcRepository(mapper(), unmapper(), TableDescription("messages", "id")) { open fun findByBoundingBox(box: PGbox2d): List = jdbcOperations.query("""SELECT * FROM ${table.name} @@ -21,34 +19,26 @@ open class MessageRepository : JdbcRepository(MessageRowMapper(), ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y} , 4326)""", rowMapper) - class MessageRowMapper : RowMapper { - val userRowMapper = UserRepository.UserRowMaper() - override fun mapRow(rs: ResultSet, rowNum: Int) - = Message( - rs.getString("content"), - userRowMapper.mapRow(rs, rowNum), - (rs.getObject("location") as PGgeometry?)?.geometry as Point?, - rs.getLong("id")) - } - - class MessageRowUnmapper : RowUnmapper { - override fun mapColumns(message: Message): Map { - val map = mutableMapOf( - Pair("id", message.id), - Pair("content", message.content), - Pair("author", message.author?.userName) - ) - if (message.location != null) { - message.location!!.srid = 4326 - map["location"] = PGgeometry(message.location) - } - return map - } - } - override fun postCreate(entity: S, generatedId: Number?): S { if (generatedId != null) entity.id = generatedId.toLong() return entity } - +} + +private fun mapper() = RowMapper { + rs, rowNum -> Message( + rs.getString("content"), + rs.getString("author"), + (rs.getObject("location") as PGgeometry?)?.geometry as Point?, + rs.getLong("id")) +} + +private fun unmapper() = RowUnmapper { + m -> + val map = mutableMapOf(Pair("id", m.id), Pair("content", m.content), Pair("author", m.author)) + if (m.location != null) { + m.location!!.srid = 4326 + map["location"] = PGgeometry(m.location) + } + map } diff --git a/src/main/kotlin/io/spring/messenger/repository/UserRepository.kt b/src/main/kotlin/io/spring/messenger/repository/UserRepository.kt index 9575ef5..a8d2f4b 100644 --- a/src/main/kotlin/io/spring/messenger/repository/UserRepository.kt +++ b/src/main/kotlin/io/spring/messenger/repository/UserRepository.kt @@ -8,10 +8,9 @@ import org.postgis.PGgeometry import org.postgis.Point import org.springframework.jdbc.core.RowMapper import org.springframework.stereotype.Repository -import java.sql.ResultSet @Repository -open class UserRepository : JdbcRepository(UserRowMaper(), UserRowUnmapper(), "\"users\"", "user_name") { +open class UserRepository : JdbcRepository(mapper(), unmapper(), "\"users\"", "user_name") { open fun updateLocation(userName:String, location: Point): Unit { location.srid = 4326 @@ -24,29 +23,25 @@ open class UserRepository : JdbcRepository(UserRowMaper(), UserRow ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y} , 4326)""", rowMapper) - class UserRowMaper : RowMapper { - override fun mapRow(rs: ResultSet, rowNum: Int) - = User( - rs.getString("user_name"), - rs.getString("first_name"), - rs.getString("last_name"), - (rs.getObject("location") as PGgeometry?)?.geometry as Point?) - } - - class UserRowUnmapper() : RowUnmapper { - override fun mapColumns(user: User): Map { - val map = mutableMapOf( - Pair("user_name", user.userName), - Pair("first_name", user.firstName), - Pair("last_name", user.lastName)) - if (user.location != null) { - user.location!!.srid = 4326 - map["location"] = PGgeometry(user.location) - } - return map; - } - } - - - } + +private fun mapper() = RowMapper { + rs, rowNum -> User( + rs.getString("user_name"), + rs.getString("first_name"), + rs.getString("last_name"), + (rs.getObject("location") as PGgeometry?)?.geometry as Point?) +} + +private fun unmapper() = RowUnmapper { + user -> + val map = mutableMapOf( + Pair("user_name", user.userName), + Pair("first_name", user.firstName), + Pair("last_name", user.lastName)) + if (user.location != null) { + user.location!!.srid = 4326 + map["location"] = PGgeometry(user.location) + } + map; +} \ No newline at end of file diff --git a/src/main/kotlin/io/spring/messenger/web/MessageController.kt b/src/main/kotlin/io/spring/messenger/web/MessageController.kt index 83fe3b5..4aac316 100644 --- a/src/main/kotlin/io/spring/messenger/web/MessageController.kt +++ b/src/main/kotlin/io/spring/messenger/web/MessageController.kt @@ -1,22 +1,32 @@ package io.spring.messenger.web +import io.spring.messenger.domain.Message import io.spring.messenger.repository.MessageRepository import org.postgis.PGbox2d +import org.postgis.Point import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.* +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter @RestController @RequestMapping("/message") class MessageController @Autowired constructor(val repository: MessageRepository) { + val broadcaster = SseBroadcaster() + + @PostMapping + fun create(@RequestBody message: Message) = repository.create(message) + @GetMapping fun findMessages() = repository.findAll() - /** - * {@code box} parameter should use this format: xmin%20ymin,xmax%20ymax - */ - @GetMapping("/bbox/{box}") - fun findByBoundingBox(@PathVariable box:String) - = repository.findByBoundingBox(PGbox2d("BOX($box)")) + @GetMapping("/bbox/{xMin},{yMin},{xMax},{yMax}") + fun findByBoundingBox(@PathVariable userName:String, @PathVariable xMin:Double, + @PathVariable yMin:Double, @PathVariable xMax:Double, @PathVariable yMax:Double) + = repository.findByBoundingBox(PGbox2d(Point(xMin, yMin), Point(xMax, yMax))) + @GetMapping("/subscribe") + fun subscribe(): SseEmitter { + return broadcaster.subscribe(); + } } \ No newline at end of file diff --git a/src/main/kotlin/io/spring/messenger/web/UserController.kt b/src/main/kotlin/io/spring/messenger/web/UserController.kt index 4774028..0905216 100644 --- a/src/main/kotlin/io/spring/messenger/web/UserController.kt +++ b/src/main/kotlin/io/spring/messenger/web/UserController.kt @@ -12,20 +12,18 @@ import org.springframework.web.bind.annotation.* class UserController @Autowired constructor(val repository: UserRepository) { @GetMapping - fun findUsers() = repository.findAll() + fun findAll() = repository.findAll() @PostMapping - fun createUser(@RequestBody user: User) = repository.create(user) + fun create(@RequestBody user: User) = repository.create(user) - @PutMapping("/{userName}/location") @ResponseStatus(NO_CONTENT) - fun updateLocation(@PathVariable userName:String, @RequestBody location: Point) - = repository.updateLocation(userName, location) + @PutMapping("/{userName}/location/{x},{y}") @ResponseStatus(NO_CONTENT) + fun updateLocation(@PathVariable userName:String, @PathVariable x: Double, @PathVariable y: Double) + = repository.updateLocation(userName, Point(x, y)) - /** - * {@code box} parameter should use this format: xmin%20ymin,xmax%20ymax - */ - @GetMapping("/bbox/{box}") - fun findByBoundingBox(@PathVariable userName:String, @PathVariable box:String) - = repository.findByBoundingBox(PGbox2d("BOX($box)")) + @GetMapping("/bbox/{xMin},{yMin},{xMax},{yMax}") + fun findByBoundingBox(@PathVariable userName:String, @PathVariable xMin:Double, + @PathVariable yMin:Double, @PathVariable xMax:Double, @PathVariable yMax:Double) + = repository.findByBoundingBox(PGbox2d(Point(xMin, yMin), Point(xMax, yMax))) } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4496f15..7b5af6f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,6 @@ +logging: + level: + org.springframework.web: DEBUG spring: datasource: platform: "postgis" diff --git a/src/main/resources/static/horse.png b/src/main/resources/static/horse.png new file mode 100644 index 0000000..c9540b2 Binary files /dev/null and b/src/main/resources/static/horse.png differ diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 35d6166..c27efaa 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -1,21 +1,18 @@ - Localized OpenStreetMap + Geospatial Messenger + + +
- -

- position accuracy :    - altitude :    - altitude accuracy :    - heading :    - speed : -

-  0 selected features + diff --git a/src/main/resources/static/map.js b/src/main/resources/static/map.js index 1fc0732..9bdf65a 100644 --- a/src/main/resources/static/map.js +++ b/src/main/resources/static/map.js @@ -1,61 +1,74 @@ -function el(id) { - return document.getElementById(id); -} +var userName = "swhite"; // Currently hardcoded + +// #################################### Map #################################### var view = new ol.View({ zoom: 8 }); -var geolocation = new ol.Geolocation({ - projection: view.getProjection() -}); +var container = document.getElementById('popup'); +var content = document.getElementById('popup-content'); + +var overlay = new ol.Overlay(({ + element: container, autoPan: true, autoPanAnimation: { + duration: 250 + } +})); var map = new ol.Map({ layers: [new ol.layer.Tile({ - source: new ol.source.OSM() - })], target: 'map', controls: ol.control.defaults({ - attributionOptions: /** @type {olx.control.AttributionOptions} */ ({ + source: new ol.source.MapQuest({layer: 'osm'}) + })], target: 'map', overlays: [overlay], controls: ol.control.defaults({ + attributionOptions: ({ collapsible: false }) }), view: view }); -geolocation.on('change', function () { - el('accuracy').innerText = geolocation.getAccuracy() + ' [m]'; - el('altitude').innerText = geolocation.getAltitude() + ' [m]'; - el('altitudeAccuracy').innerText = geolocation.getAltitudeAccuracy() + ' [m]'; - el('heading').innerText = geolocation.getHeading() + ' [rad]'; - el('speed').innerText = geolocation.getSpeed() + ' [m/s]'; -}); +// ################################# Geolocation ################################# -// handle geolocation error. +var geolocation = new ol.Geolocation({ + projection: view.getProjection() +}); geolocation.on('error', function (error) { - var info = document.getElementById('info'); - info.innerHTML = error.message; - info.style.display = ''; + alert(error.message); }); - var positionFeature = new ol.Feature(); - +positionFeature.setStyle(new ol.style.Style({ + image: new ol.style.Icon({src: "horse.png", scale: 0.25}) +})); +var centerDefined = false; geolocation.on('change:position', function () { var coordinates = geolocation.getPosition(); + $.ajax({ + url: "/user/" + userName + "swhite/location/" + coordinates[0] + "," + coordinates[1], type: "PUT" + }); + if (!centerDefined) { + view.setCenter(coordinates); + centerDefined = true; + } positionFeature.setGeometry(coordinates ? new ol.geom.Point(coordinates) : null); - view.setCenter(coordinates); }); - new ol.layer.Vector({ map: map, source: new ol.source.Vector({ features: [positionFeature] }) }); - geolocation.setTracking(true); -var select = new ol.interaction.Select(); -map.addInteraction(select); -select.on('select', function (e) { - el('status').innerHTML = ' ' + e.target.getFeatures().getLength() + ' selected features'; +// ################################# Popup ################################# +map.on('singleclick', function (evt) { + var coordinate = evt.coordinate; + var hdms = ol.coordinate.toStringHDMS(ol.proj.transform(coordinate, 'EPSG:3857', 'EPSG:4326')); + var message = '

You clicked here:

' + hdms + ''; + content.innerHTML = message; + overlay.setPosition(coordinate); + $.ajax({ + method: "POST", + url: "/message", + data: JSON.stringify({content: message, author: userName, location: {type: "Point", coordinates:[coordinate[0],coordinate[1]]}}), + contentType:"application/json; charset=utf-8", + dataType:"json" + }); }); - - diff --git a/src/main/resources/static/ping.png b/src/main/resources/static/ping.png new file mode 100644 index 0000000..8916cdf Binary files /dev/null and b/src/main/resources/static/ping.png differ diff --git a/src/main/resources/static/style.css b/src/main/resources/static/style.css new file mode 100644 index 0000000..a2b4b26 --- /dev/null +++ b/src/main/resources/static/style.css @@ -0,0 +1,47 @@ +.ol-popup { + position: absolute; + background-color: white; + -webkit-filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2)); + filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2)); + padding: 15px; + border-radius: 10px; + border: 1px solid #cccccc; + bottom: 12px; + left: -50px; + min-width: 280px; +} + +.ol-popup:after, .ol-popup:before { + top: 100%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +.ol-popup:after { + border-top-color: white; + border-width: 10px; + left: 48px; + margin-left: -10px; +} + +.ol-popup:before { + border-top-color: #cccccc; + border-width: 11px; + left: 48px; + margin-left: -11px; +} + +.ol-popup-closer { + text-decoration: none; + position: absolute; + top: 2px; + right: 8px; +} + +.ol-popup-closer:after { + content: "✖"; +} \ No newline at end of file