mirror of
https://github.com/jlengrand/geospatial-messenger.git
synced 2026-03-10 08:21:17 +00:00
Pretty big update
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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<Message, Long>(MessageRowMapper(), MessageRowUnmapper(),
|
||||
TableDescription("messages", "messages JOIN users ON messages.author = users.user_name", "id")) {
|
||||
open class MessageRepository : JdbcRepository<Message, Long>(mapper(), unmapper(), TableDescription("messages", "id")) {
|
||||
|
||||
open fun findByBoundingBox(box: PGbox2d): List<Message>
|
||||
= jdbcOperations.query("""SELECT * FROM ${table.name}
|
||||
@@ -21,34 +19,26 @@ open class MessageRepository : JdbcRepository<Message, Long>(MessageRowMapper(),
|
||||
ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y}
|
||||
, 4326)""", rowMapper)
|
||||
|
||||
class MessageRowMapper : RowMapper<Message> {
|
||||
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<Message> {
|
||||
override fun mapColumns(message: Message): Map<String, Any?> {
|
||||
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 <S : Message> postCreate(entity: S, generatedId: Number?): S {
|
||||
if (generatedId != null) entity.id = generatedId.toLong()
|
||||
return entity
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun mapper() = RowMapper<Message> {
|
||||
rs, rowNum -> Message(
|
||||
rs.getString("content"),
|
||||
rs.getString("author"),
|
||||
(rs.getObject("location") as PGgeometry?)?.geometry as Point?,
|
||||
rs.getLong("id"))
|
||||
}
|
||||
|
||||
private fun unmapper() = RowUnmapper<Message> {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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<User, String>(UserRowMaper(), UserRowUnmapper(), "\"users\"", "user_name") {
|
||||
open class UserRepository : JdbcRepository<User, String>(mapper(), unmapper(), "\"users\"", "user_name") {
|
||||
|
||||
open fun updateLocation(userName:String, location: Point): Unit {
|
||||
location.srid = 4326
|
||||
@@ -24,29 +23,25 @@ open class UserRepository : JdbcRepository<User, String>(UserRowMaper(), UserRow
|
||||
ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y}
|
||||
, 4326)""", rowMapper)
|
||||
|
||||
class UserRowMaper : RowMapper<User> {
|
||||
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<User> {
|
||||
override fun mapColumns(user: User): Map<String, Any> {
|
||||
val map = mutableMapOf<String, Any>(
|
||||
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<User> {
|
||||
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> {
|
||||
user ->
|
||||
val map = mutableMapOf<String, Any>(
|
||||
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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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)))
|
||||
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
logging:
|
||||
level:
|
||||
org.springframework.web: DEBUG
|
||||
spring:
|
||||
datasource:
|
||||
platform: "postgis"
|
||||
|
||||
BIN
src/main/resources/static/horse.png
Normal file
BIN
src/main/resources/static/horse.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 201 KiB |
@@ -1,21 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Localized OpenStreetMap</title>
|
||||
<title>Geospatial Messenger</title>
|
||||
<link rel="stylesheet" href="http://openlayers.org/en/v3.14.2/css/ol.css" type="text/css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="http://openlayers.org/en/v3.14.2/build/ol.js"></script>
|
||||
<script src="http://code.jquery.com/jquery-2.2.1.min.js"></script>
|
||||
<script src="http://www.appelsiini.net/download/jquery.jeditable.mini.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map" class="map"></div>
|
||||
<div id="info" style="display: none;"></div>
|
||||
<p>
|
||||
position accuracy : <code id="accuracy"></code>
|
||||
altitude : <code id="altitude"></code>
|
||||
altitude accuracy : <code id="altitudeAccuracy"></code>
|
||||
heading : <code id="heading"></code>
|
||||
speed : <code id="speed"></code>
|
||||
</p>
|
||||
<span id="status"> 0 selected features</span>
|
||||
<div id="popup" class="ol-popup">
|
||||
<div id="popup-content"></div>
|
||||
</div>
|
||||
<script src="map.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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 = '<p>You clicked here:</p><code>' + hdms + '</code>';
|
||||
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"
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
BIN
src/main/resources/static/ping.png
Normal file
BIN
src/main/resources/static/ping.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 653 KiB |
47
src/main/resources/static/style.css
Normal file
47
src/main/resources/static/style.css
Normal file
@@ -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: "✖";
|
||||
}
|
||||
Reference in New Issue
Block a user