background tiles
This commit is contained in:
parent
a23905c826
commit
e1daee038a
@ -0,0 +1,7 @@
|
|||||||
|
# Wundertile
|
||||||
|
|
||||||
|
## Other sketch libs:
|
||||||
|
* sketchpad 5.1: https://sketch.io/sketchpad/
|
||||||
|
* wpaint: http://wpaint.websanova.com/ (jQuery)
|
||||||
|
* sketchpad: https://yiom.github.io/sketchpad/ (animation)
|
||||||
|
* literally canvas: http://literallycanvas.com/
|
@ -18,8 +18,8 @@ tiles_bp = Blueprint('tiles', __name__)
|
|||||||
|
|
||||||
def valid_tile(z, x, y):
|
def valid_tile(z, x, y):
|
||||||
"""Returns true if the tile coordinates are valid"""
|
"""Returns true if the tile coordinates are valid"""
|
||||||
# check z in [0, 22]
|
# check z in [0, 14]
|
||||||
if z < 0 or z > 22:
|
if z < 0 or z > 14:
|
||||||
return False
|
return False
|
||||||
# check x in [0, 2^zoom[
|
# check x in [0, 2^zoom[
|
||||||
if x < 0 or x >= 2**z:
|
if x < 0 or x >= 2**z:
|
||||||
|
File diff suppressed because one or more lines are too long
@ -35,7 +35,34 @@ main {
|
|||||||
height: 256px;
|
height: 256px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.transparent-tile-bg {
|
||||||
|
background: url('/static/transparentBg.png');
|
||||||
|
}
|
||||||
|
|
||||||
.current-tile-upload {
|
.current-tile-upload {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: white;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-image {
|
||||||
|
position: absolute;
|
||||||
|
/* top: 0; */
|
||||||
|
/* left: 0; */
|
||||||
|
/* bottom: 0; */
|
||||||
|
/* right: 0; */
|
||||||
|
width: 256px;
|
||||||
|
height: 256px;
|
||||||
|
/* z-index: -1; */
|
||||||
}
|
}
|
BIN
app/static/transparentBg.png
Normal file
BIN
app/static/transparentBg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 386 B |
@ -21,15 +21,11 @@ class CurrentTileComponent extends Component {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick (e) {
|
|
||||||
console.log(this.scratchpad.image)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
class SubmitDrawingForm extends Component {
|
class SubmitDrawingForm extends Component {
|
||||||
render (props) {
|
render (props) {
|
||||||
console.log('submDrawing', props)
|
|
||||||
return (
|
return (
|
||||||
(
|
(
|
||||||
<div className='uploadForm'>
|
<div className='uploadForm'>
|
||||||
@ -47,14 +43,14 @@ class SubmitDrawingForm extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
handleSubmit (e) {
|
handleSubmit (e) {
|
||||||
console.log(this.file, this.scratchpad)
|
|
||||||
this.file.value = this.scratchpad.image
|
this.file.value = this.scratchpad.image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class UploadForm extends Component {
|
class UploadForm extends Component {
|
||||||
render (props) {
|
render (props) {
|
||||||
console.log(props)
|
|
||||||
return (
|
return (
|
||||||
<div className='uploadForm'>
|
<div className='uploadForm'>
|
||||||
<form action={props.tilePath} method='POST' enctype='multipart/form-data'>
|
<form action={props.tilePath} method='POST' enctype='multipart/form-data'>
|
||||||
|
@ -1,32 +1,53 @@
|
|||||||
import { map as LMap, tileLayer } from 'leaflet'
|
import { reaction } from 'mobx'
|
||||||
|
import { map as LMap, control } from 'leaflet'
|
||||||
import 'leaflet/dist/leaflet.css'
|
import 'leaflet/dist/leaflet.css'
|
||||||
|
|
||||||
import state from './state.js'
|
import state from './state.js'
|
||||||
|
import { OpacityControl } from './opacityControl.js'
|
||||||
|
import { wundertiles, baseLayers } from './tileLayers.js'
|
||||||
|
|
||||||
export const map = LMap('map')
|
export const map = LMap('map', {
|
||||||
|
maxZoom: 14
|
||||||
|
})
|
||||||
map.setView([0, 0], 1)
|
map.setView([0, 0], 1)
|
||||||
|
|
||||||
// background tilelayer (OSM)
|
control.layers(baseLayers).addTo(map)
|
||||||
tileLayer('https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', {
|
control.scale().addTo(map)
|
||||||
maxZoom: 18,
|
new OpacityControl({ 'position': 'bottomright' }).addTo(map)
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
wundertiles.addTo(map)
|
||||||
opacity: 0.9
|
|
||||||
}).addTo(map)
|
|
||||||
// wundertiles tilelayer
|
|
||||||
tileLayer('/tiles/{z}/{x}/{y}', {
|
|
||||||
attribution: '',
|
|
||||||
maxZoom: 22
|
|
||||||
}).addTo(map)
|
|
||||||
|
|
||||||
// compute Tile Coordinates
|
// compute Tile Coordinates
|
||||||
const toRad = (num) => num * Math.PI / 180
|
const toRad = (num) => num * Math.PI / 180
|
||||||
|
const modulo = (n, mod) => ((n % mod) + mod) % mod
|
||||||
const getTileCoords = (lat, lon, zoom) => {
|
const getTileCoords = (lat, lon, zoom) => {
|
||||||
|
let coordMax = Math.pow(2, zoom) - 1
|
||||||
let xtile = parseInt(Math.floor((lon + 180) / 360 * (1 << zoom)))
|
let xtile = parseInt(Math.floor((lon + 180) / 360 * (1 << zoom)))
|
||||||
let ytile = parseInt(Math.floor((1 - Math.log(Math.tan(toRad(lat)) + 1 / Math.cos(toRad(lat))) / Math.PI) / 2 * (1 << zoom)))
|
let ytile = parseInt(Math.floor((1 - Math.log(Math.tan(toRad(lat)) + 1 / Math.cos(toRad(lat))) / Math.PI) / 2 * (1 << zoom)))
|
||||||
|
xtile = modulo(xtile, coordMax + 1)
|
||||||
|
ytile = modulo(ytile, coordMax + 1)
|
||||||
return { x: xtile, y: ytile, z: zoom }
|
return { x: xtile, y: ytile, z: zoom }
|
||||||
}
|
}
|
||||||
|
|
||||||
// demo: how to get tile coords on click
|
// get tile coords on click
|
||||||
map.on('click', (e) => {
|
map.on('click', (e) => {
|
||||||
state.coords = getTileCoords(e.latlng.lat, e.latlng.lng, map.getZoom())
|
state.coords = getTileCoords(e.latlng.lat, e.latlng.lng, map.getZoom())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// react on base layer change
|
||||||
|
map.on('baselayerchange', (e) => {
|
||||||
|
state.backgroundTileServerUrl = e.layer._url
|
||||||
|
wundertiles.bringToFront()
|
||||||
|
e.layer.setOpacity(state.backgroundOpacity)
|
||||||
|
})
|
||||||
|
|
||||||
|
// react on opacity change
|
||||||
|
reaction(
|
||||||
|
() => state.backgroundOpacity,
|
||||||
|
opacity => {
|
||||||
|
map.eachLayer((l) => {
|
||||||
|
if (l.options.name !== 'wundertiles') {
|
||||||
|
l.setOpacity(opacity)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
31
web/src/opacityControl.js
Normal file
31
web/src/opacityControl.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Control, DomUtil, DomEvent } from 'leaflet'
|
||||||
|
|
||||||
|
import state from './state.js'
|
||||||
|
|
||||||
|
export class OpacityControl extends Control {
|
||||||
|
onAdd (map) {
|
||||||
|
const container = DomUtil.create('div', 'leaflet-bar leaflet-control opacity-control')
|
||||||
|
container.setAttribute('id', 'opacityControl')
|
||||||
|
const label = DomUtil.create('label', '', container)
|
||||||
|
label.innerText = 'Background opacity: '
|
||||||
|
const slider = DomUtil.create('input', '', container)
|
||||||
|
slider.setAttribute('type', 'range')
|
||||||
|
slider.addEventListener('input', (e) => {
|
||||||
|
state.backgroundOpacity = Number(e.target.value) / 100
|
||||||
|
})
|
||||||
|
|
||||||
|
// container.innerHTML = `<label>Background opacity: </label><input type="range">`
|
||||||
|
|
||||||
|
// prevent map dragging when mouse is over control.
|
||||||
|
// Otherwise slider-adjustments would pan map.
|
||||||
|
DomEvent.on(container, 'mousedown mouseup click touchstart', DomEvent.stopPropagation)
|
||||||
|
DomEvent.on(container, 'mouseenter', (e) => { map.dragging.disable() })
|
||||||
|
DomEvent.on(container, 'mouseleave', (e) => { map.dragging.enable() })
|
||||||
|
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
|
||||||
|
test () {
|
||||||
|
console.log('lala')
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +1,61 @@
|
|||||||
import { Component, h } from 'preact'
|
import { Component, h } from 'preact'
|
||||||
|
import { observer } from 'mobx-preact'
|
||||||
|
|
||||||
|
import state from './state.js'
|
||||||
|
|
||||||
|
@observer
|
||||||
export class Scratchpad extends Component {
|
export class Scratchpad extends Component {
|
||||||
draw = false
|
draw = false
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
let bgTileStyele = {
|
||||||
|
backgroundImage: `url(${state.backgroundTileUrl})`,
|
||||||
|
opacity: state.backgroundOpacity,
|
||||||
|
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<canvas
|
<div className='tile-image' style={{ position: '' }}>
|
||||||
width='256' height='256'
|
<div className='background-image transparent-tile-bg' />
|
||||||
className='tile-image'
|
<div className='background-image' style={bgTileStyele} />
|
||||||
onMouseDown={(e) => this.handleMouseDown(e)}
|
<canvas
|
||||||
onMouseMove={(e) => this.handleMouseMove(e)}
|
className='background-image'
|
||||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
width='256' height='256'
|
||||||
onMouseOut={(e) => this.handleMouseOut(e)}
|
onMouseDown={(e) => this.handleMouseDown(e)}
|
||||||
ref={c => { this.canvas = c }}
|
onMouseMove={(e) => this.handleMouseMove(e)}
|
||||||
/>
|
onMouseUp={(e) => this.handleMouseUp(e)}
|
||||||
|
onMouseOut={(e) => this.handleMouseOut(e)}
|
||||||
|
onMouseEnter={(e) => this.handleMouseEnter(e)}
|
||||||
|
onTouchStart={(e) => this.handleTouchStart(e)}
|
||||||
|
onTouchEnd={(e) => this.handleTouchEnd(e)}
|
||||||
|
onTouchMove={(e) => this.handleTouchMove(e)}
|
||||||
|
ref={c => { this.canvas = c }}
|
||||||
|
style={{ position: 'absolute'}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<label>Thickness: </label>
|
||||||
|
<input
|
||||||
|
type='range'
|
||||||
|
onChange={(e) => this.handleThicknessInput(e)}
|
||||||
|
min='1' max='100' value='10' />
|
||||||
|
<br />
|
||||||
|
<label>Color: </label>
|
||||||
|
<input
|
||||||
|
type='color'
|
||||||
|
onChange={(e) => this.handleColorChange(e)} />
|
||||||
|
<br />
|
||||||
|
<button onClick={() => this.clear()}>Clear</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.ctx.lineWidth = 10
|
||||||
|
this.ctx.lineJoin = 'round'
|
||||||
|
this.ctx.lineCap = 'round'
|
||||||
|
}
|
||||||
|
|
||||||
get ctx () {
|
get ctx () {
|
||||||
return this.canvas.getContext('2d')
|
return this.canvas.getContext('2d')
|
||||||
}
|
}
|
||||||
@ -27,21 +64,72 @@ export class Scratchpad extends Component {
|
|||||||
return this.canvas.toDataURL()
|
return this.canvas.toDataURL()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown (e) {
|
getCoords (e) {
|
||||||
this.draw = true
|
// handle touchevents
|
||||||
this.ctx.beginPath()
|
e = e.touches ? e.touches[0] : e
|
||||||
this.ctx.moveTo(e.layerX, e.layerY)
|
return {
|
||||||
|
x: e.pageX - this.canvas.offsetLeft,
|
||||||
|
y: e.pageY - this.canvas.offsetTop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMouseDown (e) {
|
||||||
|
let { x, y } = this.getCoords(e)
|
||||||
|
this.draw = true
|
||||||
|
this.ctx.lineWidth = this.thickness
|
||||||
|
this.ctx.beginPath()
|
||||||
|
this.ctx.moveTo(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
handleMouseMove (e) {
|
handleMouseMove (e) {
|
||||||
if (this.draw === true) {
|
if (this.draw === true) {
|
||||||
this.ctx.lineTo(e.layerX, e.layerY)
|
let { x, y } = this.getCoords(e)
|
||||||
|
this.ctx.lineTo(x, y)
|
||||||
this.ctx.stroke()
|
this.ctx.stroke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp (e) {
|
||||||
this.draw = false
|
this.draw = false
|
||||||
}
|
}
|
||||||
handleMouseOut (e) {
|
|
||||||
|
|
||||||
|
handleMouseOut (e) {
|
||||||
|
this.handleMouseUp(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEnter (e) {
|
||||||
|
if (e.buttons) {
|
||||||
|
this.handleMouseDown(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleThicknessInput (e) {
|
||||||
|
this.ctx.lineWidth = parseInt(e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleColorChange (e) {
|
||||||
|
this.ctx.strokeStyle = e.target.value
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTouchStart (e) {
|
||||||
|
if (!e.cancelable) return
|
||||||
|
e.preventDefault()
|
||||||
|
this.handleMouseDown(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTouchEnd (e) {
|
||||||
|
if (!e.cancelable) return
|
||||||
|
e.preventDefault()
|
||||||
|
this.handleMouseUp(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTouchMove (e) {
|
||||||
|
if (!e.cancelable) return
|
||||||
|
e.preventDefault()
|
||||||
|
this.handleMouseMove(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear () {
|
||||||
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
import { observable } from 'mobx'
|
import { observable, computed } from 'mobx'
|
||||||
|
|
||||||
class State {
|
class State {
|
||||||
@observable coords = { x: 0, y: 0, z: 0 }
|
@observable coords = { x: 0, y: 0, z: 0 }
|
||||||
|
@observable backgroundTileServerUrl = ''
|
||||||
|
@observable backgroundOpacity = 0.5
|
||||||
|
|
||||||
setState (newState) {
|
@computed
|
||||||
this.state = { ...this.state, ...newState }
|
get backgroundTileUrl () {
|
||||||
|
let { x, y, z } = this.coords
|
||||||
|
let url = this.backgroundTileServerUrl.replace('{s}', 'a')
|
||||||
|
url = url.replace('{x}', x)
|
||||||
|
url = url.replace('{y}', y)
|
||||||
|
url = url.replace('{z}', z)
|
||||||
|
return url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
27
web/src/tileLayers.js
Normal file
27
web/src/tileLayers.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { tileLayer } from 'leaflet'
|
||||||
|
|
||||||
|
const osmDE = tileLayer('https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 18,
|
||||||
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
})
|
||||||
|
|
||||||
|
const osmMap = tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
})
|
||||||
|
|
||||||
|
const esriSat = tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
||||||
|
attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const wundertiles = tileLayer('/tiles/{z}/{x}/{y}', {
|
||||||
|
name: 'wundertiles',
|
||||||
|
attribution: '',
|
||||||
|
maxZoom: 14
|
||||||
|
})
|
||||||
|
|
||||||
|
export const baseLayers = {
|
||||||
|
'OSM DE': osmDE,
|
||||||
|
'OSM Map': osmMap,
|
||||||
|
'ESRI Sattelite': esriSat
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user