-
+
{{ $t("map." + mapViewer.mapState) }}
@@ -50,7 +50,7 @@ export default {
width: 100%;
height: 100%;
- z-index: 100; // put over bluemap markers
+ z-index: 10000; // put over bluemap markers
pointer-events: none;
diff --git a/BlueMapCommon/webapp/src/components/Menu/MarkerSet.vue b/BlueMapCommon/webapp/src/components/Menu/MarkerSet.vue
index 82fd0435..cbe4f507 100644
--- a/BlueMapCommon/webapp/src/components/Menu/MarkerSet.vue
+++ b/BlueMapCommon/webapp/src/components/Menu/MarkerSet.vue
@@ -65,6 +65,7 @@ export default {
if (this.markerSet.toggleable) {
// eslint-disable-next-line vue/no-mutating-props
this.markerSet.visible = !this.markerSet.visible
+ this.markerSet.saveState();
}
},
more(event) {
diff --git a/BlueMapCommon/webapp/src/js/BlueMap.js b/BlueMapCommon/webapp/src/js/BlueMap.js
index 6cb5f086..8db2cc54 100644
--- a/BlueMapCommon/webapp/src/js/BlueMap.js
+++ b/BlueMapCommon/webapp/src/js/BlueMap.js
@@ -22,41 +22,85 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-import {Object3D} from "three";
+import { Object3D } from "three";
-export * from "./MapViewer";
+export * as Three from "three";
+export * from "./controls/freeflight/FreeFlightControls";
+export * from "./controls/freeflight/keyboard/KeyHeightControls";
+// class name conflicts with map controls
+export { KeyMoveControls as FreeFlightKeyMoveControls } from "./controls/freeflight/keyboard/KeyMoveControls";
+export { MouseAngleControls as FreeFlightMouseAngleControls } from "./controls/freeflight/mouse/MouseAngleControls";
+export { MouseRotateControls as FreeFlightMouseRotateControls } from "./controls/freeflight/mouse/MouseRotateControls";
+export * from "./controls/freeflight/touch/TouchPanControls";
+
+export * from "./controls/map/MapControls";
+export * from "./controls/map/MapHeightControls";
+export * from "./controls/map/keyboard/KeyAngleControls";
+export { KeyMoveControls as MapKeyMoveControls } from "./controls/map/keyboard/KeyMoveControls";
+export * from "./controls/map/keyboard/KeyRotateControls";
+export * from "./controls/map/keyboard/KeyZoomControls";
+export { MouseAngleControls as MapMouseAngleControls } from "./controls/map/mouse/MouseAngleControls";
+export * from "./controls/map/mouse/MouseMoveControls";
+export { MouseRotateControls as MapMouseRotateControls } from "./controls/map/mouse/MouseRotateControls";
+export * from "./controls/map/mouse/MouseZoomControls";
+export * from "./controls/map/touch/TouchAngleControls";
+export * from "./controls/map/touch/TouchMoveControls";
+export * from "./controls/map/touch/TouchRotateControls";
+export * from "./controls/map/touch/TouchZoomControls";
+
+export * from "./controls/ControlsManager";
+export * from "./controls/KeyCombination";
+
+export * from "./map/LowresTileLoader";
export * from "./map/Map";
export * from "./map/Tile";
export * from "./map/TileLoader";
export * from "./map/TileManager";
export * from "./map/TileMap";
+export * from "./map/hires/HiresFragmentShader";
+export * from "./map/hires/HiresVertexShader";
+export * from "./map/lowres/LowresFragmentShader";
+export * from "./map/lowres/LowresVertexShader";
export * from "./markers/ExtrudeMarker";
export * from "./markers/HtmlMarker";
export * from "./markers/LineMarker";
export * from "./markers/Marker";
+export * from "./markers/MarkerFillFragmentShader";
+export * from "./markers/MarkerFillVertexShader";
export * from "./markers/MarkerManager";
export * from "./markers/MarkerSet";
-export * from "./markers/PlayerMarkerSet";
+export * from "./markers/NormalMarkerManager";
export * from "./markers/ObjectMarker";
export * from "./markers/PlayerMarker";
+export * from "./markers/PlayerMarkerManager";
+export * from "./markers/PlayerMarkerSet";
export * from "./markers/PoiMarker";
export * from "./markers/ShapeMarker";
-export * from "./controls/map/MapControls";
-export * from "./controls/freeflight/FreeFlightControls";
+export * from "./skybox/SkyFragmentShader";
+export * from "./skybox/SkyVertexShader";
+export * from "./skybox/SkyboxScene";
+export * from "./util/CSS2DRenderer";
export * from "./util/CombinedCamera";
+export * from "./util/LineShader";
+export * from "./util/Stats";
export * from "./util/Utils";
+export * from "./BlueMapApp";
+export * from "./MainMenu";
+export * from "./MapViewer";
+export * from "./PopupMarker";
+export * from "./Utils";
+
/**
* @param event {object}
* @return {boolean} - whether the event has been consumed (true) or not (false)
*/
-Object3D.prototype.onClick = function(event) {
-
- if (this.parent){
+Object3D.prototype.onClick = function (event) {
+ if (this.parent) {
if (!Array.isArray(event.eventStack)) event.eventStack = [];
event.eventStack.push(this);
diff --git a/BlueMapCommon/webapp/src/js/BlueMapApp.js b/BlueMapCommon/webapp/src/js/BlueMapApp.js
index ff9d673a..75b233d9 100644
--- a/BlueMapCommon/webapp/src/js/BlueMapApp.js
+++ b/BlueMapCommon/webapp/src/js/BlueMapApp.js
@@ -48,7 +48,7 @@ export class BlueMapApp {
this.mapViewer = new MapViewer(rootElement, this.events);
- this.mapControls = new MapControls(this.mapViewer.renderer.domElement);
+ this.mapControls = new MapControls(this.mapViewer.renderer.domElement, rootElement);
this.freeFlightControls = new FreeFlightControls(this.mapViewer.renderer.domElement);
/** @type {PlayerMarkerManager} */
@@ -251,6 +251,9 @@ export class BlueMapApp {
let map = this.mapsMap.get(mapId);
if (!map) return Promise.reject(`There is no map with the id "${mapId}" loaded!`);
+ if (this.playerMarkerManager) this.playerMarkerManager.dispose();
+ if (this.markerFileManager) this.markerFileManager.dispose();
+
await this.mapViewer.switchMap(map)
if (resetCamera) this.resetCamera();
@@ -353,10 +356,8 @@ export class BlueMapApp {
}
initPlayerMarkerManager() {
- if (this.playerMarkerManager){
- this.playerMarkerManager.clear();
+ if (this.playerMarkerManager)
this.playerMarkerManager.dispose()
- }
const map = this.mapViewer.map;
if (!map) return;
@@ -369,16 +370,13 @@ export class BlueMapApp {
})
.catch(e => {
alert(this.events, e, "warning");
- this.playerMarkerManager.clear();
this.playerMarkerManager.dispose();
});
}
initMarkerFileManager() {
- if (this.markerFileManager) {
- this.markerFileManager.clear();
+ if (this.markerFileManager)
this.markerFileManager.dispose();
- }
const map = this.mapViewer.map;
if (!map) return;
@@ -390,7 +388,6 @@ export class BlueMapApp {
})
.catch(e => {
alert(this.events, e, "warning");
- this.markerFileManager.clear();
this.markerFileManager.dispose();
});
}
diff --git a/BlueMapCommon/webapp/src/js/controls/freeflight/FreeFlightControls.js b/BlueMapCommon/webapp/src/js/controls/freeflight/FreeFlightControls.js
index 17f876a7..746cccf5 100644
--- a/BlueMapCommon/webapp/src/js/controls/freeflight/FreeFlightControls.js
+++ b/BlueMapCommon/webapp/src/js/controls/freeflight/FreeFlightControls.js
@@ -23,7 +23,7 @@
* THE SOFTWARE.
*/
-import {MathUtils, Vector2} from "three";
+import {MathUtils, Vector2, Vector3} from "three";
import {Manager, Pan, DIRECTION_ALL} from "hammerjs";
import {animate, EasingFunctions} from "../../util/Utils";
import {KeyMoveControls} from "./keyboard/KeyMoveControls";
@@ -32,9 +32,12 @@ import {MouseAngleControls} from "./mouse/MouseAngleControls";
import {KeyHeightControls} from "./keyboard/KeyHeightControls";
import {TouchPanControls} from "./touch/TouchPanControls";
import {reactive} from "vue";
+import {DEG2RAD} from "three/src/math/MathUtils";
export class FreeFlightControls {
+ static _beforeMoveTemp = new Vector3();
+
/**
* @param target {Element}
*/
@@ -43,7 +46,7 @@ export class FreeFlightControls {
this.manager = null;
this.data = reactive({
-
+ followingPlayer: null
});
this.hammer = new Manager(this.target);
@@ -99,12 +102,32 @@ export class FreeFlightControls {
* @param map {Map}
*/
update(delta, map) {
+ FreeFlightControls._beforeMoveTemp.copy(this.manager.position);
+ let beforeMoveRot = this.manager.rotation;
+ let beforeMoveAngle = this.manager.angle;
+
this.keyMove.update(delta, map);
this.keyHeight.update(delta, map);
this.mouseRotate.update(delta, map);
this.mouseAngle.update(delta, map);
this.touchPan.update(delta, map);
+ // if moved, stop following the marker and give back control
+ if (this.data.followingPlayer && (
+ !FreeFlightControls._beforeMoveTemp.equals(this.manager.position) ||
+ beforeMoveRot !== this.manager.rotation ||
+ beforeMoveAngle !== this.manager.angle
+ )) {
+ this.stopFollowingPlayerMarker();
+ }
+
+ // follow player marker
+ if (this.data.followingPlayer) {
+ this.manager.position.copy(this.data.followingPlayer.position);
+ this.manager.rotation = (this.data.followingPlayer.rotation.yaw - 180) * DEG2RAD;
+ this.manager.angle = -(this.data.followingPlayer.rotation.pitch - 90) * DEG2RAD;
+ }
+
this.manager.angle = MathUtils.clamp(this.manager.angle, 0, Math.PI);
this.manager.distance = 0;
this.manager.ortho = 0;
@@ -133,6 +156,19 @@ export class FreeFlightControls {
});
}
+ /**
+ * @param marker {object}
+ */
+ followPlayerMarker(marker) {
+ if (marker.isPlayerMarker) marker = marker.data;
+ this.data.followingPlayer = marker;
+ this.keyMove.deltaPosition.set(0, 0);
+ }
+
+ stopFollowingPlayerMarker() {
+ this.data.followingPlayer = null;
+ }
+
onWheel = evt => {
evt.preventDefault();
diff --git a/BlueMapCommon/webapp/src/js/controls/freeflight/keyboard/KeyHeightControls.js b/BlueMapCommon/webapp/src/js/controls/freeflight/keyboard/KeyHeightControls.js
index 7b4ea6c3..1013d1eb 100644
--- a/BlueMapCommon/webapp/src/js/controls/freeflight/keyboard/KeyHeightControls.js
+++ b/BlueMapCommon/webapp/src/js/controls/freeflight/keyboard/KeyHeightControls.js
@@ -66,11 +66,13 @@ export class KeyHeightControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
+ window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
+ window.removeEventListener("blur", this.onStop)
}
/**
@@ -120,4 +122,9 @@ export class KeyHeightControls {
}
}
+ onStop = evt => {
+ this.up = false;
+ this.down = false;
+ }
+
}
\ No newline at end of file
diff --git a/BlueMapCommon/webapp/src/js/controls/freeflight/keyboard/KeyMoveControls.js b/BlueMapCommon/webapp/src/js/controls/freeflight/keyboard/KeyMoveControls.js
index 81509d9b..f64ddbf6 100644
--- a/BlueMapCommon/webapp/src/js/controls/freeflight/keyboard/KeyMoveControls.js
+++ b/BlueMapCommon/webapp/src/js/controls/freeflight/keyboard/KeyMoveControls.js
@@ -78,11 +78,13 @@ export class KeyMoveControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
+ window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
+ window.removeEventListener("blur", this.onStop)
}
/**
@@ -152,4 +154,11 @@ export class KeyMoveControls {
}
}
+ onStop = evt => {
+ this.up = false;
+ this.down = false;
+ this.left = false;
+ this.right = false;
+ }
+
}
\ No newline at end of file
diff --git a/BlueMapCommon/webapp/src/js/controls/map/MapControls.js b/BlueMapCommon/webapp/src/js/controls/map/MapControls.js
index 7d22150c..f835a6b5 100644
--- a/BlueMapCommon/webapp/src/js/controls/map/MapControls.js
+++ b/BlueMapCommon/webapp/src/js/controls/map/MapControls.js
@@ -50,9 +50,11 @@ export class MapControls {
/**
* @param rootElement {Element}
+ * @param scrollCaptureElement {Element}
*/
- constructor(rootElement) {
+ constructor(rootElement, scrollCaptureElement) {
this.rootElement = rootElement;
+ this.scrollCaptureElement = scrollCaptureElement;
this.data = reactive({
followingPlayer: null
@@ -68,7 +70,7 @@ export class MapControls {
this.mouseMove = new MouseMoveControls(this.rootElement, 1.5,0.3);
this.mouseRotate = new MouseRotateControls(this.rootElement, 6, 0.3);
this.mouseAngle = new MouseAngleControls(this.rootElement, 3, 0.3);
- this.mouseZoom = new MouseZoomControls(this.rootElement, 1, 0.2);
+ this.mouseZoom = new MouseZoomControls(this.scrollCaptureElement, 1, 0.2);
this.keyMove = new KeyMoveControls(this.rootElement, 0.025, 0.2);
this.keyRotate = new KeyRotateControls(this.rootElement, 0.06, 0.15);
diff --git a/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyAngleControls.js b/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyAngleControls.js
index 2e2e063f..fcdb2529 100644
--- a/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyAngleControls.js
+++ b/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyAngleControls.js
@@ -67,11 +67,13 @@ export class KeyAngleControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
+ window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
+ window.removeEventListener("blur", this.onStop)
}
/**
@@ -121,4 +123,9 @@ export class KeyAngleControls {
}
}
+ onStop = evt => {
+ this.up = false;
+ this.down = false;
+ }
+
}
\ No newline at end of file
diff --git a/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyMoveControls.js b/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyMoveControls.js
index 30aaf4a6..a91aaed0 100644
--- a/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyMoveControls.js
+++ b/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyMoveControls.js
@@ -78,11 +78,13 @@ export class KeyMoveControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
+ window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
+ window.removeEventListener("blur", this.onStop)
}
/**
@@ -152,4 +154,11 @@ export class KeyMoveControls {
}
}
+ onStop = evt => {
+ this.up = false;
+ this.down = false;
+ this.left = false;
+ this.right = false;
+ }
+
}
\ No newline at end of file
diff --git a/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyRotateControls.js b/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyRotateControls.js
index 6ed2c4e7..1413182b 100644
--- a/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyRotateControls.js
+++ b/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyRotateControls.js
@@ -67,11 +67,13 @@ export class KeyRotateControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
+ window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
+ window.removeEventListener("blur", this.onStop)
}
/**
@@ -121,4 +123,9 @@ export class KeyRotateControls {
}
}
+ onStop = evt => {
+ this.left = false;
+ this.right = false;
+ }
+
}
\ No newline at end of file
diff --git a/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyZoomControls.js b/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyZoomControls.js
index 0c884dfd..dd9a5b16 100644
--- a/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyZoomControls.js
+++ b/BlueMapCommon/webapp/src/js/controls/map/keyboard/KeyZoomControls.js
@@ -65,11 +65,13 @@ export class KeyZoomControls {
window.addEventListener("keydown", this.onKeyDown);
window.addEventListener("keyup", this.onKeyUp);
+ window.addEventListener("blur", this.onStop)
}
stop() {
window.removeEventListener("keydown", this.onKeyDown);
window.removeEventListener("keyup", this.onKeyUp);
+ window.removeEventListener("blur", this.onStop)
}
/**
@@ -119,4 +121,9 @@ export class KeyZoomControls {
}
}
+ onStop = evt => {
+ this.in = false;
+ this.out = false;
+ }
+
}
\ No newline at end of file
diff --git a/BlueMapCommon/webapp/src/js/markers/MarkerManager.js b/BlueMapCommon/webapp/src/js/markers/MarkerManager.js
index cbdd4ba5..e9dbb12b 100644
--- a/BlueMapCommon/webapp/src/js/markers/MarkerManager.js
+++ b/BlueMapCommon/webapp/src/js/markers/MarkerManager.js
@@ -83,7 +83,8 @@ export class MarkerManager {
*/
update() {
return this.loadMarkerFile()
- .then(markerFileData => this.updateFromData(markerFileData));
+ .then(markerFileData => this.updateFromData(markerFileData))
+ .catch(() => this.clear());
}
/**
diff --git a/BlueMapCommon/webapp/src/js/markers/MarkerSet.js b/BlueMapCommon/webapp/src/js/markers/MarkerSet.js
index f3aecf8b..d4719dcb 100644
--- a/BlueMapCommon/webapp/src/js/markers/MarkerSet.js
+++ b/BlueMapCommon/webapp/src/js/markers/MarkerSet.js
@@ -30,13 +30,14 @@ import {LineMarker} from "./LineMarker";
import {HtmlMarker} from "./HtmlMarker";
import {PoiMarker} from "./PoiMarker";
import {reactive} from "vue";
+import {getLocalStorage, setLocalStorage} from "../Utils";
export class MarkerSet extends Scene {
/**
* @param id {string}
*/
- constructor(id) {
+ constructor(id, data = null) {
super();
Object.defineProperty(this, 'isMarkerSet', {value: true});
@@ -58,6 +59,9 @@ export class MarkerSet extends Scene {
return this.toggleable ||
this.markers.filter(marker => marker.listed).length > 0 ||
this.markerSets.filter(markerSet => markerSet.listed).length > 0
+ },
+ saveState: () => {
+ setLocalStorage(this.localStorageKey("visible"), this.visible);
}
});
@@ -65,6 +69,19 @@ export class MarkerSet extends Scene {
get() { return this.data.visible },
set(value) { this.data.visible = value }
});
+
+ if (data) {
+ this.updateFromData(data);
+ }
+
+ if (this.data.toggleable) {
+ let storedVisible = getLocalStorage(this.localStorageKey("visible"));
+ if (storedVisible !== undefined) {
+ this.visible = !!storedVisible;
+ } else if (this.data.defaultHide) {
+ this.visible = false;
+ }
+ }
}
updateFromData(data) {
@@ -108,18 +125,14 @@ export class MarkerSet extends Scene {
updateMarkerSetFromData(markerSetId, data) {
let markerSet = this.markerSets.get(markerSetId);
- // create new if not existent
if (!markerSet) {
- markerSet = new MarkerSet(markerSetId);
+ // create new if not existent
+ markerSet = new MarkerSet(markerSetId, data);
this.add(markerSet);
-
- if (data.defaultHidden) {
- markerSet.visible = false;
- }
+ } else {
+ // update
+ markerSet.updateFromData(data);
}
-
- // update
- markerSet.updateFromData(data);
}
updateMarkersFromData(data = {}, ignore = []) {
@@ -174,8 +187,8 @@ export class MarkerSet extends Scene {
* Removes all markers and marker-sets
*/
clear() {
- [...this.data.markerSets].forEach(markerSet => this.remove(markerSet));
- [...this.data.markers].forEach(marker => this.remove(marker));
+ [...this.markerSets.values()].forEach(markerSet => this.remove(markerSet));
+ [...this.markers.values()].forEach(marker => this.remove(marker));
}
add(...object) {
@@ -220,4 +233,8 @@ export class MarkerSet extends Scene {
});
}
+ localStorageKey(key) {
+ return "bluemap-markerset-" + encodeURIComponent(this.data.id) + "-" + key;
+ }
+
}
\ No newline at end of file
diff --git a/BlueMapCommon/webapp/src/js/markers/NormalMarkerManager.js b/BlueMapCommon/webapp/src/js/markers/NormalMarkerManager.js
index d35721ac..a9d82167 100644
--- a/BlueMapCommon/webapp/src/js/markers/NormalMarkerManager.js
+++ b/BlueMapCommon/webapp/src/js/markers/NormalMarkerManager.js
@@ -48,4 +48,8 @@ export class NormalMarkerManager extends MarkerManager {
return true;
}
+ clear() {
+ this.root.updateMarkerSetsFromData({}, [PLAYER_MARKER_SET_ID, "bm-popup-set"]);
+ }
+
}
\ No newline at end of file
diff --git a/BlueMapCommon/webapp/src/js/markers/PlayerMarker.js b/BlueMapCommon/webapp/src/js/markers/PlayerMarker.js
index 5e29dfa8..d8d80642 100644
--- a/BlueMapCommon/webapp/src/js/markers/PlayerMarker.js
+++ b/BlueMapCommon/webapp/src/js/markers/PlayerMarker.js
@@ -41,6 +41,10 @@ export class PlayerMarker extends Marker {
this.data.playerUuid = playerUuid;
this.data.name = playerUuid;
this.data.playerHead = playerHead;
+ this.data.rotation = {
+ pitch: 0,
+ yaw: 0
+ };
this.elementObject = new CSS2DObject(htmlToElement(`
@@ -102,24 +106,34 @@ export class PlayerMarker extends Marker {
// animate position update
let pos = markerData.position || {};
+ let rot = markerData.rotation || {};
if (!this.position.x && !this.position.y && !this.position.z) {
this.position.set(
pos.x || 0,
(pos.y || 0) + 1.8,
pos.z || 0
);
+ this.data.rotation.pitch = rot.pitch || 0;
+ this.data.rotation.yaw = rot.yaw || 0;
} else {
let startPos = {
x: this.position.x,
y: this.position.y,
z: this.position.z,
+ pitch: this.data.rotation.pitch,
+ yaw: this.data.rotation.yaw,
}
let deltaPos = {
x: (pos.x || 0) - startPos.x,
y: ((pos.y || 0) + 1.8) - startPos.y,
z: (pos.z || 0) - startPos.z,
+ pitch: (rot.pitch || 0) - startPos.pitch,
+ yaw: (rot.yaw || 0) - startPos.yaw
}
- if (deltaPos.x || deltaPos.y || deltaPos.z) {
+ while (deltaPos.yaw > 180) deltaPos.yaw -= 360;
+ while (deltaPos.yaw < -180) deltaPos.yaw += 360;
+
+ if (deltaPos.x || deltaPos.y || deltaPos.z || deltaPos.pitch || deltaPos.yaw) {
animate(progress => {
let ease = EasingFunctions.easeInOutCubic(progress);
this.position.set(
@@ -127,7 +141,9 @@ export class PlayerMarker extends Marker {
startPos.y + deltaPos.y * ease || 0,
startPos.z + deltaPos.z * ease || 0
);
- }, 500);
+ this.data.rotation.pitch = startPos.pitch + deltaPos.pitch * ease || 0;
+ this.data.rotation.yaw = startPos.yaw + deltaPos.yaw * ease || 0;
+ }, 1000);
}
}
diff --git a/BlueMapCommon/webapp/src/js/markers/PlayerMarkerManager.js b/BlueMapCommon/webapp/src/js/markers/PlayerMarkerManager.js
index 84dbcbd1..b114c8a4 100644
--- a/BlueMapCommon/webapp/src/js/markers/PlayerMarkerManager.js
+++ b/BlueMapCommon/webapp/src/js/markers/PlayerMarkerManager.js
@@ -78,4 +78,8 @@ export class PlayerMarkerManager extends MarkerManager {
return this.getPlayerMarkerSet().getPlayerMarker(playerUuid)
}
+ clear() {
+ this.getPlayerMarkerSet(false).clear();
+ }
+
}
\ No newline at end of file
diff --git a/BlueMapCommon/webapp/src/js/markers/PlayerMarkerSet.js b/BlueMapCommon/webapp/src/js/markers/PlayerMarkerSet.js
index 9008e933..1883358c 100644
--- a/BlueMapCommon/webapp/src/js/markers/PlayerMarkerSet.js
+++ b/BlueMapCommon/webapp/src/js/markers/PlayerMarkerSet.js
@@ -29,8 +29,8 @@ import {PlayerMarker} from "./PlayerMarker";
export class PlayerMarkerSet extends MarkerSet {
- constructor(id, playerheadsUrl) {
- super(id);
+ constructor(id, playerheadsUrl, data = null) {
+ super(id, data);
this.data.label = "Player";
this.data.toggleable = true;
this.data.defaultHide = false;
diff --git a/BlueMapCommon/webapp/src/main.js b/BlueMapCommon/webapp/src/main.js
index 3d6c63a1..467a6c47 100644
--- a/BlueMapCommon/webapp/src/main.js
+++ b/BlueMapCommon/webapp/src/main.js
@@ -25,6 +25,7 @@
import * as Vue from 'vue';
import App from './App.vue';
+import * as BlueMap from "./js/BlueMap";
import {BlueMapApp} from "./js/BlueMapApp";
import {i18nModule, loadLanguageSettings} from "./i18n";
@@ -38,6 +39,7 @@ async function load() {
try {
const bluemap = new BlueMapApp(document.getElementById("map-container"));
window.bluemap = bluemap;
+ window.BlueMap = BlueMap;
// init vue
const vue = Vue.createApp(App, {
diff --git a/BlueMapCommon/webapp/src/scss/markers.scss b/BlueMapCommon/webapp/src/scss/markers.scss
index 820940c7..4c5c3fde 100644
--- a/BlueMapCommon/webapp/src/scss/markers.scss
+++ b/BlueMapCommon/webapp/src/scss/markers.scss
@@ -27,6 +27,8 @@
.bm-marker-html {
position: relative;
+ user-select: none;
+
.bm-marker-poi-label {
position: absolute;
top: 0;
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java
index b4c62766..57258e7c 100644
--- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java
@@ -219,7 +219,7 @@ public synchronized void saveMarkerState() {
public synchronized void savePlayerState() {
try (
- OutputStream out = storage.writeMeta(id, META_FILE_PLAYERS);
+ OutputStream out = storage.writeMeta(id, META_FILE_PLAYERS)
) {
out.write("{}".getBytes(StandardCharsets.UTF_8));
} catch (Exception ex) {
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java
index 668fc26d..5adeabdf 100644
--- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java
@@ -37,6 +37,7 @@
public class MapRenderState {
private final Map
regionRenderTimes;
+ private transient long latestRenderTime = -1;
public MapRenderState() {
regionRenderTimes = new HashMap<>();
@@ -44,6 +45,13 @@ public MapRenderState() {
public synchronized void setRenderTime(Vector2i regionPos, long renderTime) {
regionRenderTimes.put(regionPos, renderTime);
+
+ if (latestRenderTime != -1) {
+ if (renderTime > latestRenderTime)
+ latestRenderTime = renderTime;
+ else
+ latestRenderTime = -1;
+ }
}
public synchronized long getRenderTime(Vector2i regionPos) {
@@ -52,6 +60,19 @@ public synchronized long getRenderTime(Vector2i regionPos) {
else return renderTime;
}
+ public long getLatestRenderTime() {
+ if (latestRenderTime == -1) {
+ synchronized (this) {
+ latestRenderTime = regionRenderTimes.values().stream()
+ .mapToLong(Long::longValue)
+ .max()
+ .orElse(-1);
+ }
+ }
+
+ return latestRenderTime;
+ }
+
public synchronized void reset() {
regionRenderTimes.clear();
}
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java
index 9f90a990..35752b08 100644
--- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java
@@ -55,7 +55,9 @@ public ChunkAnvil113(MCAWorld world, CompoundTag chunkTag) {
CompoundTag levelData = chunkTag.getCompoundTag("Level");
String status = levelData.getString("Status");
- this.isGenerated = status.equals("full");
+ this.isGenerated = status.equals("full") ||
+ status.equals("fullchunk") ||
+ status.equals("postprocessed");
this.hasLight = isGenerated;
this.inhabitedTime = levelData.getLong("InhabitedTime");
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/MySQLStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/MySQLStorage.java
new file mode 100644
index 00000000..1f99693e
--- /dev/null
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/MySQLStorage.java
@@ -0,0 +1,13 @@
+package de.bluecolored.bluemap.core.storage.sql;
+
+import de.bluecolored.bluemap.core.storage.sql.dialect.MySQLDialect;
+
+import java.net.MalformedURLException;
+
+public class MySQLStorage extends SQLStorage{
+
+ public MySQLStorage(SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
+ super(MySQLDialect.INSTANCE, config);
+ }
+
+}
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/PostgreSQLStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/PostgreSQLStorage.java
new file mode 100644
index 00000000..85825337
--- /dev/null
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/PostgreSQLStorage.java
@@ -0,0 +1,120 @@
+package de.bluecolored.bluemap.core.storage.sql;
+
+import com.flowpowered.math.vector.Vector2i;
+import de.bluecolored.bluemap.core.storage.CompressedInputStream;
+import de.bluecolored.bluemap.core.storage.Compression;
+import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
+import de.bluecolored.bluemap.core.storage.sql.dialect.PostgresDialect;
+import de.bluecolored.bluemap.core.util.WrappedOutputStream;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Optional;
+
+public class PostgreSQLStorage extends SQLStorage {
+
+ public PostgreSQLStorage(SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
+ super(PostgresDialect.INSTANCE, config);
+ }
+
+ public PostgreSQLStorage(Dialect dialect, SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
+ super(dialect, config);
+ }
+
+ @Override
+ public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IOException {
+ Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ return new WrappedOutputStream(compression.compress(byteOut), () -> {
+ int mapFK = getMapFK(mapId);
+ int tileCompressionFK = getMapTileCompressionFK(compression);
+
+ recoveringConnection(connection -> {
+ executeUpdate(connection, this.dialect.writeMapTile(),
+ mapFK,
+ lod,
+ tile.getX(),
+ tile.getY(),
+ tileCompressionFK,
+ byteOut.toByteArray()
+ );
+ }, 2);
+ });
+ }
+
+ @Override
+ public OutputStream writeMeta(String mapId, String name) {
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ return new WrappedOutputStream(byteOut, () -> {
+ int mapFK = getMapFK(mapId);
+ recoveringConnection(connection -> {
+ executeUpdate(connection, this.dialect.writeMeta(),
+ mapFK,
+ name,
+ byteOut.toByteArray()
+ );
+ }, 2);
+ });
+ }
+
+ @Override
+ public Optional readMapTile(String mapId, int lod, Vector2i tile) throws IOException {
+ Compression compression = lod == 0 ? this.hiresCompression : Compression.NONE;
+
+ try {
+ byte[] data = recoveringConnection(connection -> {
+ ResultSet result = executeQuery(connection, this.dialect.readMapTile(),
+ mapId,
+ lod,
+ tile.getX(),
+ tile.getY(),
+ compression.getTypeId()
+ );
+
+ if (result.next()) {
+ return result.getBytes(1);
+ } else {
+ return null;
+ }
+ }, 2);
+
+ if (data == null) {
+ return Optional.empty();
+ }
+
+ InputStream inputStream = new ByteArrayInputStream(data);
+ return Optional.of(new CompressedInputStream(inputStream, compression));
+ } catch (SQLException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public Optional readMeta(String mapId, String name) throws IOException {
+ try {
+ byte[] data = recoveringConnection(connection -> {
+ ResultSet result = executeQuery(connection, this.dialect.readMeta(),
+ mapId,
+ escapeMetaName(name)
+ );
+ if (result.next()) {
+ return result.getBytes(1);
+ } else {
+ return null;
+ }
+ }, 2);
+
+ if (data == null) {
+ return Optional.empty();
+ }
+
+ InputStream inputStream = new ByteArrayInputStream(data);
+ return Optional.of(inputStream);
+ } catch (SQLException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+}
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java
index 2ec91c36..3af604ac 100644
--- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLStorage.java
@@ -30,6 +30,8 @@
import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.storage.*;
+import de.bluecolored.bluemap.core.storage.sql.dialect.DialectType;
+import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
import de.bluecolored.bluemap.core.util.WrappedOutputStream;
import org.apache.commons.dbcp2.*;
import org.apache.commons.pool2.ObjectPool;
@@ -48,10 +50,12 @@
import java.util.concurrent.CompletionException;
import java.util.function.Function;
-public class SQLStorage extends Storage {
+public abstract class SQLStorage extends Storage {
private final DataSource dataSource;
- private final Compression hiresCompression;
+
+ protected final Dialect dialect;
+ protected final Compression hiresCompression;
private final LoadingCache mapFKs = Caffeine.newBuilder()
.executor(BlueMap.THREAD_POOL)
@@ -62,9 +66,9 @@ public class SQLStorage extends Storage {
private volatile boolean closed;
- public SQLStorage(SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
+ public SQLStorage(Dialect dialect, SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
+ this.dialect = dialect;
this.closed = false;
-
try {
if (config.getDriverClass().isPresent()) {
if (config.getDriverJar().isPresent()) {
@@ -115,9 +119,7 @@ public OutputStream writeMapTile(String mapId, int lod, Vector2i tile) throws IO
byteOut.writeTo(blobOut);
}
- executeUpdate(connection,
- "REPLACE INTO `bluemap_map_tile` (`map`, `lod`, `x`, `z`, `compression`, `data`) " +
- "VALUES (?, ?, ?, ?, ?, ?)",
+ executeUpdate(connection, this.dialect.writeMapTile(),
mapFK,
lod,
tile.getX(),
@@ -139,17 +141,7 @@ public Optional readMapTile(String mapId, int lod, Vector
try {
byte[] data = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection,
- "SELECT t.`data` " +
- "FROM `bluemap_map_tile` t " +
- " INNER JOIN `bluemap_map` m " +
- " ON t.`map` = m.`id` " +
- " INNER JOIN `bluemap_map_tile_compression` c " +
- " ON t.`compression` = c.`id` " +
- "WHERE m.`map_id` = ? " +
- "AND t.`lod` = ? " +
- "AND t.`x` = ? " +
- "AND t.`z` = ? " +
- "AND c.`compression` = ?",
+ this.dialect.readMapTile(),
mapId,
lod,
tile.getX(),
@@ -179,17 +171,7 @@ public Optional readMapTileInfo(final String mapId, int lod, final Vec
try {
TileInfo tileInfo = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection,
- "SELECT t.`changed`, LENGTH(t.`data`) as 'size' " +
- "FROM `bluemap_map_tile` t " +
- " INNER JOIN `bluemap_map` m " +
- " ON t.`map` = m.`id` " +
- " INNER JOIN `bluemap_map_tile_compression` c " +
- " ON t.`compression` = c.`id` " +
- "WHERE m.`map_id` = ? " +
- "AND t.`lod` = ? " +
- "AND t.`x` = ? " +
- "AND t.`z` = ? " +
- "AND c.`compression` = ?",
+ this.dialect.readMapTileInfo(),
mapId,
lod,
tile.getX(),
@@ -238,15 +220,7 @@ public long getLastModified() {
public void deleteMapTile(String mapId, int lod, Vector2i tile) throws IOException {
try {
recoveringConnection(connection ->
- executeUpdate(connection,
- "DELETE t " +
- "FROM `bluemap_map_tile` t " +
- " INNER JOIN `bluemap_map` m " +
- " ON t.`map` = m.`id` " +
- "WHERE m.`map_id` = ? " +
- "AND t.`lod` = ? " +
- "AND t.`x` = ? " +
- "AND t.`z` = ?",
+ executeUpdate(connection,this.dialect.deleteMapTile(),
mapId,
lod,
tile.getX(),
@@ -271,8 +245,7 @@ public OutputStream writeMeta(String mapId, String name) {
}
executeUpdate(connection,
- "REPLACE INTO `bluemap_map_meta` (`map`, `key`, `value`) " +
- "VALUES (?, ?, ?)",
+ this.dialect.writeMeta(),
mapFK,
escapeMetaName(name),
dataBlob
@@ -289,12 +262,7 @@ public Optional readMeta(String mapId, String name) throws IOExcept
try {
byte[] data = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection,
- "SELECT t.`value` " +
- "FROM `bluemap_map_meta` t " +
- " INNER JOIN `bluemap_map` m " +
- " ON t.`map` = m.`id` " +
- "WHERE m.`map_id` = ? " +
- "AND t.`key` = ?",
+ this.dialect.readMeta(),
mapId,
escapeMetaName(name)
);
@@ -319,12 +287,7 @@ public Optional readMetaInfo(String mapId, String name) throws IOExcep
try {
MetaInfo tileInfo = recoveringConnection(connection -> {
ResultSet result = executeQuery(connection,
- "SELECT LENGTH(t.`value`) as 'size' " +
- "FROM `bluemap_map_meta` t " +
- " INNER JOIN `bluemap_map` m " +
- " ON t.`map` = m.`id` " +
- "WHERE m.`map_id` = ? " +
- "AND t.`key` = ?",
+ this.dialect.readMetaSize(),
mapId,
escapeMetaName(name)
);
@@ -361,12 +324,7 @@ public void deleteMeta(String mapId, String name) throws IOException {
try {
recoveringConnection(connection ->
executeUpdate(connection,
- "DELETE t " +
- "FROM `bluemap_map_meta` t " +
- " INNER JOIN `bluemap_map` m " +
- " ON t.`map` = m.`id` " +
- "WHERE m.`map_id` = ? " +
- "AND t.`key` = ?",
+ this.dialect.deleteMeta(),
mapId,
escapeMetaName(name)
), 2);
@@ -381,28 +339,18 @@ public void purgeMap(String mapId, Function onProgress) t
try {
recoveringConnection(connection -> {
executeUpdate(connection,
- "DELETE t " +
- "FROM `bluemap_map_tile` t " +
- " INNER JOIN `bluemap_map` m " +
- " ON t.`map` = m.`id` " +
- "WHERE m.`map_id` = ?",
+ this.dialect.purgeMapTile(),
mapId
);
executeUpdate(connection,
- "DELETE t " +
- "FROM `bluemap_map_meta` t " +
- " INNER JOIN `bluemap_map` m " +
- " ON t.`map` = m.`id` " +
- "WHERE m.`map_id` = ?",
+ this.dialect.purgeMapMeta(),
mapId
);
executeUpdate(connection,
- "DELETE " +
- "FROM `bluemap_map` " +
- "WHERE `map_id` = ?",
+ this.dialect.purgeMap(),
mapId
);
}, 2);
@@ -420,7 +368,7 @@ public Collection collectMapIds() throws IOException {
try {
return recoveringConnection(connection -> {
ResultSet result = executeQuery(connection,
- "SELECT `map_id` FROM `bluemap_map`"
+ this.dialect.selectMapIds()
);
Collection mapIds = new ArrayList<>();
while (result.next()) {
@@ -440,15 +388,10 @@ public void initialize() throws IOException {
// initialize and get schema-version
String schemaVersionString = recoveringConnection(connection -> {
connection.createStatement().executeUpdate(
- "CREATE TABLE IF NOT EXISTS `bluemap_storage_meta` (" +
- "`key` varchar(255) NOT NULL, " +
- "`value` varchar(255) DEFAULT NULL, " +
- "PRIMARY KEY (`key`)" +
- ")");
+ this.dialect.initializeStorageMeta());
ResultSet result = executeQuery(connection,
- "SELECT `value` FROM `bluemap_storage_meta` " +
- "WHERE `key` = ?",
+ this.dialect.selectStorageMeta(),
"schema_version"
);
@@ -456,8 +399,7 @@ public void initialize() throws IOException {
return result.getString("value");
} else {
executeUpdate(connection,
- "INSERT INTO `bluemap_storage_meta` (`key`, `value`) " +
- "VALUES (?, ?)",
+ this.dialect.insertStorageMeta(),
"schema_version", "0"
);
return "0";
@@ -482,51 +424,22 @@ public void initialize() throws IOException {
recoveringConnection(connection -> {
connection.createStatement().executeUpdate(
- "CREATE TABLE `bluemap_map` (" +
- "`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," +
- "`map_id` VARCHAR(255) NOT NULL," +
- "PRIMARY KEY (`id`)," +
- "UNIQUE INDEX `map_id` (`map_id`)" +
- ");"
+ this.dialect.initializeMap()
);
connection.createStatement().executeUpdate(
- "CREATE TABLE `bluemap_map_tile_compression` (" +
- "`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," +
- "`compression` VARCHAR(255) NOT NULL," +
- "PRIMARY KEY (`id`)," +
- "UNIQUE INDEX `compression` (`compression`)" +
- ");"
+ this.dialect.initializeMapTileCompression()
);
connection.createStatement().executeUpdate(
- "CREATE TABLE `bluemap_map_meta` (" +
- "`map` SMALLINT UNSIGNED NOT NULL," +
- "`key` varchar(255) NOT NULL," +
- "`value` LONGBLOB NOT NULL," +
- "PRIMARY KEY (`map`, `key`)," +
- "CONSTRAINT `fk_bluemap_map_meta_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" +
- ")");
+ this.dialect.initializeMapMeta());
connection.createStatement().executeUpdate(
- "CREATE TABLE `bluemap_map_tile` (" +
- "`map` SMALLINT UNSIGNED NOT NULL," +
- "`lod` SMALLINT UNSIGNED NOT NULL," +
- "`x` INT NOT NULL," +
- "`z` INT NOT NULL," +
- "`compression` SMALLINT UNSIGNED NOT NULL," +
- "`changed` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
- "`data` LONGBLOB NOT NULL," +
- "PRIMARY KEY (`map`, `lod`, `x`, `z`)," +
- "CONSTRAINT `fk_bluemap_map_tile_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT," +
- "CONSTRAINT `fk_bluemap_map_tile_compression` FOREIGN KEY (`compression`) REFERENCES `bluemap_map_tile_compression` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" +
- ");"
+ this.dialect.initializeMapTile()
);
executeUpdate(connection,
- "UPDATE `bluemap_storage_meta` " +
- "SET `value` = ? " +
- "WHERE `key` = ?",
+ this.dialect.updateStorageMeta(),
"3", "schema_version"
);
}, 2);
@@ -544,36 +457,27 @@ public void initialize() throws IOException {
// delete potential files that are already in the new format to avoid constraint-issues
executeUpdate(connection,
- "DELETE FROM `bluemap_map_meta`" +
- "WHERE `key` IN (?, ?, ?)",
+ this.dialect.deleteMapMeta(),
"settings.json", "textures.json", ".rstate"
);
// rename files
executeUpdate(connection,
- "UPDATE `bluemap_map_meta` " +
- "SET `key` = ? " +
- "WHERE `key` = ?",
+ this.dialect.updateMapMeta(),
"settings.json", "settings"
);
executeUpdate(connection,
- "UPDATE `bluemap_map_meta` " +
- "SET `key` = ? " +
- "WHERE `key` = ?",
+ this.dialect.updateMapMeta(),
"textures.json", "textures"
);
executeUpdate(connection,
- "UPDATE `bluemap_map_meta` " +
- "SET `key` = ? " +
- "WHERE `key` = ?",
+ this.dialect.updateMapMeta(),
".rstate", "render_state"
);
// update schemaVersion
executeUpdate(connection,
- "UPDATE `bluemap_storage_meta` " +
- "SET `value` = ? " +
- "WHERE `key` = ?",
+ this.dialect.updateStorageMeta(),
"3", "schema_version"
);
}, 2);
@@ -603,32 +507,31 @@ public void close() throws IOException {
}
}
- private ResultSet executeQuery(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
- // we only use this prepared statement once, but the DB-Driver caches those and reuses them
- PreparedStatement statement = connection.prepareStatement(sql);
- for (int i = 0; i < parameters.length; i++) {
- statement.setObject(i+1, parameters[i]);
- }
- return statement.executeQuery();
+ protected ResultSet executeQuery(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
+ return prepareStatement(connection, sql, parameters).executeQuery();
}
@SuppressWarnings("UnusedReturnValue")
- private int executeUpdate(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
+ protected int executeUpdate(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
+ return prepareStatement(connection, sql, parameters).executeUpdate();
+ }
+
+ private PreparedStatement prepareStatement(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
// we only use this prepared statement once, but the DB-Driver caches those and reuses them
PreparedStatement statement = connection.prepareStatement(sql);
for (int i = 0; i < parameters.length; i++) {
- statement.setObject(i+1, parameters[i]);
+ statement.setObject(i + 1, parameters[i]);
}
- return statement.executeUpdate();
+ return statement;
}
@SuppressWarnings("SameParameterValue")
- private void recoveringConnection(ConnectionConsumer action, int tries) throws SQLException, IOException {
+ protected void recoveringConnection(ConnectionConsumer action, int tries) throws SQLException, IOException {
recoveringConnection((ConnectionFunction) action, tries);
}
@SuppressWarnings("SameParameterValue")
- private R recoveringConnection(ConnectionFunction action, int tries) throws SQLException, IOException {
+ protected R recoveringConnection(ConnectionFunction action, int tries) throws SQLException, IOException {
SQLException sqlException = null;
try {
@@ -657,7 +560,7 @@ private R recoveringConnection(ConnectionFunction action, int tries) thro
throw sqlException;
}
- private int getMapFK(String mapId) throws SQLException {
+ protected int getMapFK(String mapId) throws SQLException {
try {
return Objects.requireNonNull(mapFKs.get(mapId));
} catch (CompletionException ex) {
@@ -670,7 +573,7 @@ private int getMapFK(String mapId) throws SQLException {
}
}
- private int getMapTileCompressionFK(Compression compression) throws SQLException {
+ int getMapTileCompressionFK(Compression compression) throws SQLException {
try {
return Objects.requireNonNull(mapTileCompressionFKs.get(compression));
} catch (CompletionException ex) {
@@ -698,9 +601,7 @@ private int lookupFK(String table, String idField, String valueField, String val
return recoveringConnection(connection -> {
int key;
ResultSet result = executeQuery(connection,
- //language=SQL
- "SELECT `" + idField + "` FROM `" + table + "` " +
- "WHERE `" + valueField + "` = ?",
+ this.dialect.lookupFK(table,idField,valueField),
value
);
@@ -708,8 +609,7 @@ private int lookupFK(String table, String idField, String valueField, String val
key = result.getInt("id");
} else {
PreparedStatement statement = connection.prepareStatement(
- "INSERT INTO `" + table + "` (`" + valueField + "`) " +
- "VALUES (?)",
+ this.dialect.insertFK(table,valueField),
Statement.RETURN_GENERATED_KEYS
);
statement.setString(1, value);
@@ -774,6 +674,12 @@ private DataSource createDataSource(ConnectionFactory connectionFactory, int max
return new PoolingDataSource<>(connectionPool);
}
+ public static SQLStorage create(SQLStorageSettings settings) throws Exception {
+ String dbUrl = settings.getConnectionUrl();
+ String provider = dbUrl.strip().split(":", 3)[1];
+ return DialectType.getStorage(provider,settings);
+ }
+
@FunctionalInterface
public interface ConnectionConsumer extends ConnectionFunction {
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLiteStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLiteStorage.java
new file mode 100644
index 00000000..4c41217a
--- /dev/null
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLiteStorage.java
@@ -0,0 +1,13 @@
+package de.bluecolored.bluemap.core.storage.sql;
+
+import de.bluecolored.bluemap.core.storage.sql.dialect.SqliteDialect;
+
+import java.net.MalformedURLException;
+
+public class SQLiteStorage extends PostgreSQLStorage {
+
+ public SQLiteStorage(SQLStorageSettings config) throws MalformedURLException, SQLDriverException {
+ super(SqliteDialect.INSTANCE, config);
+ }
+
+}
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/Dialect.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/Dialect.java
new file mode 100644
index 00000000..bf2b1c04
--- /dev/null
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/Dialect.java
@@ -0,0 +1,78 @@
+package de.bluecolored.bluemap.core.storage.sql.dialect;
+
+import org.intellij.lang.annotations.Language;
+
+public interface Dialect {
+ @Language("sql")
+ String writeMapTile();
+
+ @Language("sql")
+ String readMapTile();
+
+ @Language("sql")
+ String readMapTileInfo();
+
+ @Language("sql")
+ String deleteMapTile();
+
+ @Language("sql")
+ String writeMeta();
+
+ @Language("sql")
+ String readMeta();
+
+ @Language("sql")
+ String readMetaSize();
+
+ @Language("sql")
+ String deleteMeta();
+
+ @Language("sql")
+ String purgeMapTile();
+
+ @Language("sql")
+ String purgeMapMeta();
+
+ @Language("sql")
+ String purgeMap();
+
+ @Language("sql")
+ String selectMapIds();
+
+ @Language("sql")
+ String initializeStorageMeta();
+
+ @Language("sql")
+ String selectStorageMeta();
+
+ @Language("sql")
+ String insertStorageMeta();
+
+ @Language("sql")
+ String initializeMap();
+
+ @Language("sql")
+ String initializeMapTileCompression();
+
+ @Language("sql")
+ String initializeMapMeta();
+
+ @Language("sql")
+ String initializeMapTile();
+
+ @Language("sql")
+ String updateStorageMeta(); // can be use twice in init
+
+ @Language("sql")
+ String deleteMapMeta();
+
+ @Language("sql")
+ String updateMapMeta(); // can be used twice in init
+
+ @Language("sql")
+ String lookupFK(String table, String idField, String valueField);
+
+ @Language("sql")
+ String insertFK(String table, String valueField);
+
+}
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/DialectType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/DialectType.java
new file mode 100644
index 00000000..d0394bdd
--- /dev/null
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/DialectType.java
@@ -0,0 +1,42 @@
+package de.bluecolored.bluemap.core.storage.sql.dialect;
+
+import de.bluecolored.bluemap.core.storage.sql.*;
+
+public enum DialectType {
+
+ MYSQL (MySQLStorage::new, "mysql"),
+ MARIADB (MySQLStorage::new, "mariadb"),
+ POSTGRESQL (PostgreSQLStorage::new, "postgresql"),
+ SQLITE (SQLiteStorage::new, "sqlite");
+
+ private static final DialectType FALLBACK = MYSQL;
+
+ private final SQLStorageFactory storageFactory;
+ private final String dialectName;
+
+ DialectType(SQLStorageFactory storageFactory, String dialectName) {
+ this.storageFactory = storageFactory;
+ this.dialectName = dialectName;
+ }
+ public String getDialectName() {
+ return dialectName;
+ }
+
+ public static SQLStorage getStorage(String dialectName, SQLStorageSettings settings) throws Exception {
+ for (DialectType dialect : values()) {
+ if (dialect.getDialectName().equals(dialectName)) {
+ return dialect.storageFactory.provide(settings);
+ }
+ }
+
+ // unknown dialect, use fallback
+ return FALLBACK.storageFactory.provide(settings);
+ }
+
+ @FunctionalInterface
+ public interface SQLStorageFactory {
+ SQLStorage provide(SQLStorageSettings config) throws Exception;
+
+ }
+
+}
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/MySQLDialect.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/MySQLDialect.java
new file mode 100644
index 00000000..bed99c34
--- /dev/null
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/MySQLDialect.java
@@ -0,0 +1,250 @@
+package de.bluecolored.bluemap.core.storage.sql.dialect;
+
+import org.intellij.lang.annotations.Language;
+
+public class MySQLDialect implements Dialect {
+
+ public static final MySQLDialect INSTANCE = new MySQLDialect();
+
+ private MySQLDialect() {};
+
+ @Override
+ @Language("MySQL")
+ public String writeMapTile() {
+ return "REPLACE INTO `bluemap_map_tile` (`map`, `lod`, `x`, `z`, `compression`, `data`) " +
+ "VALUES (?, ?, ?, ?, ?, ?)";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String readMapTile() {
+ return "SELECT t.`data` " +
+ "FROM `bluemap_map_tile` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ " INNER JOIN `bluemap_map_tile_compression` c " +
+ " ON t.`compression` = c.`id` " +
+ "WHERE m.`map_id` = ? " +
+ "AND t.`lod` = ? " +
+ "AND t.`x` = ? " +
+ "AND t.`z` = ? " +
+ "AND c.`compression` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String readMapTileInfo() {
+ return "SELECT t.`changed`, LENGTH(t.`data`) as 'size' " +
+ "FROM `bluemap_map_tile` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ " INNER JOIN `bluemap_map_tile_compression` c " +
+ " ON t.`compression` = c.`id` " +
+ "WHERE m.`map_id` = ? " +
+ "AND t.`lod` = ? " +
+ "AND t.`x` = ? " +
+ "AND t.`z` = ? " +
+ "AND c.`compression` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String deleteMapTile() {
+ return "DELETE t " +
+ "FROM `bluemap_map_tile` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ "WHERE m.`map_id` = ? " +
+ "AND t.`lod` = ? " +
+ "AND t.`x` = ? " +
+ "AND t.`z` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String writeMeta() {
+ return "REPLACE INTO `bluemap_map_meta` (`map`, `key`, `value`) " +
+ "VALUES (?, ?, ?)";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String readMeta() {
+ return "SELECT t.`value` " +
+ "FROM `bluemap_map_meta` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ "WHERE m.`map_id` = ? " +
+ "AND t.`key` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String readMetaSize() {
+ return "SELECT LENGTH(t.`value`) as 'size' " +
+ "FROM `bluemap_map_meta` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ "WHERE m.`map_id` = ? " +
+ "AND t.`key` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String deleteMeta() {
+ return "DELETE t " +
+ "FROM `bluemap_map_meta` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ "WHERE m.`map_id` = ? " +
+ "AND t.`key` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String purgeMapTile() {
+ return "DELETE t " +
+ "FROM `bluemap_map_tile` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ "WHERE m.`map_id` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String purgeMapMeta() {
+ return "DELETE t " +
+ "FROM `bluemap_map_meta` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ "WHERE m.`map_id` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String purgeMap() {
+ return "DELETE " +
+ "FROM `bluemap_map` " +
+ "WHERE `map_id` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String selectMapIds() {
+ return "SELECT `map_id` FROM `bluemap_map`";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String initializeStorageMeta() {
+ return "CREATE TABLE IF NOT EXISTS `bluemap_storage_meta` (" +
+ "`key` varchar(255) NOT NULL, " +
+ "`value` varchar(255) DEFAULT NULL, " +
+ "PRIMARY KEY (`key`)" +
+ ")";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String selectStorageMeta() {
+ return "SELECT `value` FROM `bluemap_storage_meta` " +
+ "WHERE `key` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String insertStorageMeta() {
+ return "INSERT INTO `bluemap_storage_meta` (`key`, `value`) " +
+ "VALUES (?, ?)";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String initializeMap() {
+ return "CREATE TABLE `bluemap_map` (" +
+ "`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," +
+ "`map_id` VARCHAR(255) NOT NULL," +
+ "PRIMARY KEY (`id`)," +
+ "UNIQUE INDEX `map_id` (`map_id`)" +
+ ");";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String initializeMapTileCompression() {
+ return "CREATE TABLE `bluemap_map_tile_compression` (" +
+ "`id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT," +
+ "`compression` VARCHAR(255) NOT NULL," +
+ "PRIMARY KEY (`id`)," +
+ "UNIQUE INDEX `compression` (`compression`)" +
+ ");";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String initializeMapMeta() {
+ return "CREATE TABLE `bluemap_map_meta` (" +
+ "`map` SMALLINT UNSIGNED NOT NULL," +
+ "`key` varchar(255) NOT NULL," +
+ "`value` LONGBLOB NOT NULL," +
+ "PRIMARY KEY (`map`, `key`)," +
+ "CONSTRAINT `fk_bluemap_map_meta_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" +
+ ")";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String initializeMapTile() {
+ return "CREATE TABLE `bluemap_map_tile` (" +
+ "`map` SMALLINT UNSIGNED NOT NULL," +
+ "`lod` SMALLINT UNSIGNED NOT NULL," +
+ "`x` INT NOT NULL," +
+ "`z` INT NOT NULL," +
+ "`compression` SMALLINT UNSIGNED NOT NULL," +
+ "`changed` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+ "`data` LONGBLOB NOT NULL," +
+ "PRIMARY KEY (`map`, `lod`, `x`, `z`)," +
+ "CONSTRAINT `fk_bluemap_map_tile_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT," +
+ "CONSTRAINT `fk_bluemap_map_tile_compression` FOREIGN KEY (`compression`) REFERENCES `bluemap_map_tile_compression` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" +
+ ");";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String updateStorageMeta() {
+ return "UPDATE `bluemap_storage_meta` " +
+ "SET `value` = ? " +
+ "WHERE `key` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String deleteMapMeta() {
+ return "DELETE FROM `bluemap_map_meta`" +
+ "WHERE `key` IN (?, ?, ?)";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String updateMapMeta() {
+ return "UPDATE `bluemap_map_meta` " +
+ "SET `key` = ? " +
+ "WHERE `key` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String lookupFK(String table, String idField, String valueField) {
+ return "SELECT `" + idField + "` FROM `" + table + "` " +
+ "WHERE `" + valueField + "` = ?";
+ }
+
+ @Override
+ @Language("MySQL")
+ public String insertFK(String table, String valueField) {
+ return "INSERT INTO `" + table + "` (`" + valueField + "`) " +
+ "VALUES (?)";
+ }
+
+
+}
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/PostgresDialect.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/PostgresDialect.java
new file mode 100644
index 00000000..0332ef8d
--- /dev/null
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/PostgresDialect.java
@@ -0,0 +1,240 @@
+package de.bluecolored.bluemap.core.storage.sql.dialect;
+
+import org.intellij.lang.annotations.Language;
+
+public class PostgresDialect implements Dialect {
+
+ public static final PostgresDialect INSTANCE = new PostgresDialect();
+
+ private PostgresDialect() {};
+
+ @Override
+ @Language("PostgreSQL")
+ public String writeMapTile() {
+ return "INSERT INTO bluemap_map_tile (map, lod, x, z, compression, data) " +
+ "VALUES (?, ?, ?, ?, ?, ?) " +
+ "ON CONFLICT (map, lod, x, z) DO UPDATE SET compression = EXCLUDED.compression, data = EXCLUDED.data";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String readMapTile() {
+ return "SELECT t.data " +
+ "FROM bluemap_map_tile t " +
+ " INNER JOIN bluemap_map m " +
+ " ON t.map = m.id " +
+ " INNER JOIN bluemap_map_tile_compression c " +
+ " ON t.compression = c.id " +
+ "WHERE m.map_id = ? " +
+ "AND t.lod = ? " +
+ "AND t.x = ? " +
+ "AND t.z = ? " +
+ "AND c.compression = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String readMapTileInfo() {
+ return "SELECT t.changed, OCTET_LENGTH(t.data) as size " +
+ "FROM bluemap_map_tile t " +
+ " INNER JOIN bluemap_map m " +
+ " ON t.map = m.id " +
+ " INNER JOIN bluemap_map_tile_compression c " +
+ " ON t.compression = c.id " +
+ "WHERE m.map_id = ? " +
+ "AND t.lod = ? " +
+ "AND t.x = ? " +
+ "AND t.z = ? " +
+ "AND c.compression = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String deleteMapTile() {
+ return "DELETE FROM bluemap_map_tile t " +
+ "USING bluemap_map m " +
+ "WHERE t.map = m.id " +
+ "AND m.map_id = ? " +
+ "AND t.lod = ? " +
+ "AND t.x = ? " +
+ "AND t.z = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String writeMeta() {
+ return "INSERT INTO bluemap_map_meta (map, key, value) " +
+ "VALUES (?, ?, ?) " +
+ "ON CONFLICT (map, key) DO UPDATE SET value = EXCLUDED.value";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String readMeta() {
+ return "SELECT t.value " +
+ "FROM bluemap_map_meta t " +
+ " INNER JOIN bluemap_map m " +
+ " ON t.map = m.id " +
+ "WHERE m.map_id = ? " +
+ "AND t.key = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String readMetaSize() {
+ return "SELECT OCTET_LENGTH(t.value) as size " +
+ "FROM bluemap_map_meta t " +
+ " INNER JOIN bluemap_map m " +
+ " ON t.map = m.id " +
+ "WHERE m.map_id = ? " +
+ "AND t.key = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String deleteMeta() {
+ return "DELETE FROM bluemap_map_meta t " +
+ "USING bluemap_map m " +
+ "WHERE t.map = m.id " +
+ "AND m.map_id = ? " +
+ "AND t.key = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String purgeMapTile() {
+ return "DELETE FROM bluemap_map_tile t " +
+ "USING bluemap_map m " +
+ "WHERE t.map = m.id " +
+ "AND m.map_id = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String purgeMapMeta() {
+ return "DELETE FROM bluemap_map_meta t " +
+ "USING bluemap_map m " +
+ "WHERE t.map = m.id " +
+ "AND m.map_id = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String purgeMap() {
+ return "DELETE FROM bluemap_map " +
+ "WHERE map_id = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String selectMapIds() {
+ return "SELECT map_id FROM bluemap_map";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String initializeStorageMeta() {
+ return "CREATE TABLE IF NOT EXISTS bluemap_storage_meta (" +
+ "key varchar(255) PRIMARY KEY, " +
+ "value varchar(255)" +
+ ")";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String selectStorageMeta() {
+ return "SELECT value FROM bluemap_storage_meta " +
+ "WHERE key = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String insertStorageMeta() {
+ return "INSERT INTO bluemap_storage_meta (key, value) " +
+ "VALUES (?, ?) " +
+ "ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String initializeMap() {
+ return "CREATE TABLE IF NOT EXISTS bluemap_map (" +
+ "id SERIAL PRIMARY KEY, " +
+ "map_id VARCHAR(255) UNIQUE NOT NULL" +
+ ")";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String initializeMapTileCompression() {
+ return "CREATE TABLE IF NOT EXISTS bluemap_map_tile_compression (" +
+ "id SERIAL PRIMARY KEY, " +
+ "compression VARCHAR(255) UNIQUE NOT NULL" +
+ ")";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String initializeMapMeta() {
+ return "CREATE TABLE IF NOT EXISTS bluemap_map_meta (" +
+ "map SMALLINT REFERENCES bluemap_map(id) ON UPDATE RESTRICT ON DELETE RESTRICT, " +
+ "key varchar(255) NOT NULL, " +
+ "value BYTEA NOT NULL, " +
+ "PRIMARY KEY (map, key)" +
+ ")";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String initializeMapTile() {
+ return "CREATE TABLE IF NOT EXISTS bluemap_map_tile (" +
+ "map SMALLINT REFERENCES bluemap_map(id) ON UPDATE RESTRICT ON DELETE RESTRICT, " +
+ "lod SMALLINT NOT NULL, " +
+ "x INT NOT NULL, " +
+ "z INT NOT NULL, " +
+ "compression SMALLINT REFERENCES bluemap_map_tile_compression(id) ON UPDATE RESTRICT ON DELETE RESTRICT, " +
+ "changed TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+ "data BYTEA NOT NULL, " +
+ "PRIMARY KEY (map, lod, x, z)" +
+ ")";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String updateStorageMeta() {
+ return "UPDATE bluemap_storage_meta " +
+ "SET value = ? " +
+ "WHERE key = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String deleteMapMeta() {
+ return "DELETE FROM bluemap_map_meta " +
+ "WHERE key IN (?, ?, ?)";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String updateMapMeta() {
+ return "UPDATE bluemap_map_meta " +
+ "SET key = ? " +
+ "WHERE key = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String lookupFK(String table, String idField, String valueField) {
+ return "SELECT " + idField + " FROM " + table +
+ " WHERE " + valueField + " = ?";
+ }
+
+ @Override
+ @Language("PostgreSQL")
+ public String insertFK(String table, String valueField) {
+ return "INSERT INTO " + table + " (" + valueField + ") " +
+ "VALUES (?)";
+ }
+
+
+}
diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/SqliteDialect.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/SqliteDialect.java
new file mode 100644
index 00000000..80157aa0
--- /dev/null
+++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/dialect/SqliteDialect.java
@@ -0,0 +1,256 @@
+package de.bluecolored.bluemap.core.storage.sql.dialect;
+
+import org.intellij.lang.annotations.Language;
+
+public class SqliteDialect implements Dialect {
+
+ public static final SqliteDialect INSTANCE = new SqliteDialect();
+
+ private SqliteDialect() {}
+
+ @Override
+ @Language("sqlite")
+ public String writeMapTile() {
+ return "REPLACE INTO `bluemap_map_tile` (`map`, `lod`, `x`, `z`, `compression`, `data`) " +
+ "VALUES (?, ?, ?, ?, ?, ?)";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String readMapTile() {
+ return "SELECT t.`data` " +
+ "FROM `bluemap_map_tile` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ " INNER JOIN `bluemap_map_tile_compression` c " +
+ " ON t.`compression` = c.`id` " +
+ "WHERE m.`map_id` = ? " +
+ "AND t.`lod` = ? " +
+ "AND t.`x` = ? " +
+ "AND t.`z` = ? " +
+ "AND c.`compression` = ?";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String readMapTileInfo() {
+ return "SELECT t.`changed`, LENGTH(t.`data`) as 'size' " +
+ "FROM `bluemap_map_tile` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ " INNER JOIN `bluemap_map_tile_compression` c " +
+ " ON t.`compression` = c.`id` " +
+ "WHERE m.`map_id` = ? " +
+ "AND t.`lod` = ? " +
+ "AND t.`x` = ? " +
+ "AND t.`z` = ? " +
+ "AND c.`compression` = ?";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String deleteMapTile() {
+ return "DELETE FROM `bluemap_map_tile` " +
+ "WHERE `map` IN( " +
+ " SELECT `id` " +
+ " FROM `bluemap_map` " +
+ " WHERE `map_id` = ? " +
+ " LIMIT 1 " +
+ ") " +
+ "AND `lod` = ? " +
+ "AND `x` = ? " +
+ "AND `z` = ? ";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String writeMeta() {
+ return "REPLACE INTO `bluemap_map_meta` (`map`, `key`, `value`) " +
+ "VALUES (?, ?, ?)";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String readMeta() {
+ return "SELECT t.`value` " +
+ "FROM `bluemap_map_meta` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ "WHERE m.`map_id` = ? " +
+ "AND t.`key` = ?";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String readMetaSize() {
+ return "SELECT LENGTH(t.`value`) as 'size' " +
+ "FROM `bluemap_map_meta` t " +
+ " INNER JOIN `bluemap_map` m " +
+ " ON t.`map` = m.`id` " +
+ "WHERE m.`map_id` = ? " +
+ "AND t.`key` = ?";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String deleteMeta() {
+ return "DELETE FROM `bluemap_map_meta` " +
+ "WHERE `map` IN( " +
+ " SELECT `id` " +
+ " FROM `bluemap_map` " +
+ " WHERE `map_id` = ? " +
+ " LIMIT 1 " +
+ ") " +
+ "AND `key` = ?";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String purgeMapTile() {
+ return "DELETE FROM `bluemap_map_tile` " +
+ "WHERE `map` IN( " +
+ " SELECT `id` " +
+ " FROM `bluemap_map` " +
+ " WHERE `map_id` = ? " +
+ " LIMIT 1 " +
+ ")";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String purgeMapMeta() {
+ return "DELETE FROM `bluemap_map_meta` " +
+ "WHERE `map` IN( " +
+ " SELECT `id` " +
+ " FROM `bluemap_map` " +
+ " WHERE `map_id` = ? " +
+ " LIMIT 1 " +
+ ")";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String purgeMap() {
+ return "DELETE " +
+ "FROM `bluemap_map` " +
+ "WHERE `map_id` = ?";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String selectMapIds() {
+ return "SELECT `map_id` FROM `bluemap_map`";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String initializeStorageMeta() {
+ return "CREATE TABLE IF NOT EXISTS `bluemap_storage_meta` (" +
+ "`key` varchar(255) NOT NULL, " +
+ "`value` varchar(255) DEFAULT NULL, " +
+ "PRIMARY KEY (`key`)" +
+ ")";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String selectStorageMeta() {
+ return "SELECT `value` FROM `bluemap_storage_meta` " +
+ "WHERE `key` = ?";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String insertStorageMeta() {
+ return "INSERT INTO `bluemap_storage_meta` (`key`, `value`) " +
+ "VALUES (?, ?)";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String initializeMap() {
+ return "CREATE TABLE `bluemap_map` (" +
+ "`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," +
+ "`map_id` VARCHAR(255) NOT NULL," +
+ "UNIQUE (`map_id`)" +
+ ");";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String initializeMapTileCompression() {
+ return "CREATE TABLE `bluemap_map_tile_compression` (" +
+ "`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," +
+ "`compression` VARCHAR(255) NOT NULL," +
+ "UNIQUE (`compression`)" +
+ ");";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String initializeMapMeta() {
+ return "CREATE TABLE `bluemap_map_meta` (" +
+ "`map` SMALLINT UNSIGNED NOT NULL," +
+ "`key` varchar(255) NOT NULL," +
+ "`value` LONGBLOB NOT NULL," +
+ "PRIMARY KEY (`map`, `key`)," +
+ "CONSTRAINT `fk_bluemap_map_meta_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" +
+ ")";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String initializeMapTile() {
+ return "CREATE TABLE `bluemap_map_tile` (" +
+ "`map` SMALLINT UNSIGNED NOT NULL," +
+ "`lod` SMALLINT UNSIGNED NOT NULL," +
+ "`x` INT NOT NULL," +
+ "`z` INT NOT NULL," +
+ "`compression` SMALLINT UNSIGNED NOT NULL," +
+ "`changed` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP," +
+ "`data` LONGBLOB NOT NULL," +
+ "PRIMARY KEY (`map`, `lod`, `x`, `z`)," +
+ "CONSTRAINT `fk_bluemap_map_tile_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT," +
+ "CONSTRAINT `fk_bluemap_map_tile_compression` FOREIGN KEY (`compression`) REFERENCES `bluemap_map_tile_compression` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT" +
+ ");";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String updateStorageMeta() {
+ return "UPDATE `bluemap_storage_meta` " +
+ "SET `value` = ? " +
+ "WHERE `key` = ?";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String deleteMapMeta() {
+ return "DELETE FROM `bluemap_map_meta`" +
+ "WHERE `key` IN (?, ?, ?)";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String updateMapMeta() {
+ return "UPDATE `bluemap_map_meta` " +
+ "SET `key` = ? " +
+ "WHERE `key` = ?";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String lookupFK(String table, String idField, String valueField) {
+ return "SELECT `" + idField + "` FROM `" + table + "` " +
+ "WHERE `" + valueField + "` = ?";
+ }
+
+ @Override
+ @Language("sqlite")
+ public String insertFK(String table, String valueField) {
+ return "INSERT INTO `" + table + "` (`" + valueField + "`) " +
+ "VALUES (?)";
+ }
+
+
+}
diff --git a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java
index 8ff7292f..55dc7909 100644
--- a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java
+++ b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java
@@ -54,7 +54,9 @@
import java.io.File;
import java.io.IOException;
+import java.net.BindException;
import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.TimeUnit;
@@ -204,12 +206,24 @@ public void startWebserver(BlueMapService blueMap, boolean verbose) throws IOExc
HttpRequestHandler handler = new BlueMapResponseModifier(routingRequestHandler);
if (verbose) handler = new LoggingRequestHandler(handler);
- HttpServer webServer = new HttpServer(handler);
- webServer.bind(new InetSocketAddress(
- config.resolveIp(),
- config.getPort()
- ));
- webServer.start();
+ try {
+ HttpServer webServer = new HttpServer(handler);
+ webServer.bind(new InetSocketAddress(
+ config.resolveIp(),
+ config.getPort()
+ ));
+ webServer.start();
+ } catch (UnknownHostException ex) {
+ throw new ConfigurationException("BlueMap failed to resolve the ip in your webserver-config.\n" +
+ "Check if that is correctly configured.", ex);
+ } catch (BindException ex) {
+ throw new ConfigurationException("BlueMap failed to bind to the configured address.\n" +
+ "This usually happens when the configured port (" + config.getPort() + ") is already in use by some other program.", ex);
+ } catch (IOException ex) {
+ throw new ConfigurationException("BlueMap failed to initialize the webserver.\n" +
+ "Check your webserver-config if everything is configured correctly.\n" +
+ "(Make sure you DON'T use the same port for bluemap that you also use for your minecraft server)", ex);
+ }
}
@Override