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):
|
||||
"""Returns true if the tile coordinates are valid"""
|
||||
# check z in [0, 22]
|
||||
if z < 0 or z > 22:
|
||||
# check z in [0, 14]
|
||||
if z < 0 or z > 14:
|
||||
return False
|
||||
# check x in [0, 2^zoom[
|
||||
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;
|
||||
}
|
||||
|
||||
.transparent-tile-bg {
|
||||
background: url('/static/transparentBg.png');
|
||||
}
|
||||
|
||||
.current-tile-upload {
|
||||
display: flex;
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
handleClick (e) {
|
||||
console.log(this.scratchpad.image)
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
class SubmitDrawingForm extends Component {
|
||||
render (props) {
|
||||
console.log('submDrawing', props)
|
||||
return (
|
||||
(
|
||||
<div className='uploadForm'>
|
||||
@ -47,14 +43,14 @@ class SubmitDrawingForm extends Component {
|
||||
)
|
||||
}
|
||||
handleSubmit (e) {
|
||||
console.log(this.file, this.scratchpad)
|
||||
this.file.value = this.scratchpad.image
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class UploadForm extends Component {
|
||||
render (props) {
|
||||
console.log(props)
|
||||
return (
|
||||
<div className='uploadForm'>
|
||||
<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 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)
|
||||
|
||||
// background tilelayer (OSM)
|
||||
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',
|
||||
opacity: 0.9
|
||||
}).addTo(map)
|
||||
// wundertiles tilelayer
|
||||
tileLayer('/tiles/{z}/{x}/{y}', {
|
||||
attribution: '',
|
||||
maxZoom: 22
|
||||
}).addTo(map)
|
||||
control.layers(baseLayers).addTo(map)
|
||||
control.scale().addTo(map)
|
||||
new OpacityControl({ 'position': 'bottomright' }).addTo(map)
|
||||
wundertiles.addTo(map)
|
||||
|
||||
// compute Tile Coordinates
|
||||
const toRad = (num) => num * Math.PI / 180
|
||||
const modulo = (n, mod) => ((n % mod) + mod) % mod
|
||||
const getTileCoords = (lat, lon, zoom) => {
|
||||
let coordMax = Math.pow(2, zoom) - 1
|
||||
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)))
|
||||
xtile = modulo(xtile, coordMax + 1)
|
||||
ytile = modulo(ytile, coordMax + 1)
|
||||
return { x: xtile, y: ytile, z: zoom }
|
||||
}
|
||||
|
||||
// demo: how to get tile coords on click
|
||||
// get tile coords on click
|
||||
map.on('click', (e) => {
|
||||
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 { observer } from 'mobx-preact'
|
||||
|
||||
import state from './state.js'
|
||||
|
||||
@observer
|
||||
export class Scratchpad extends Component {
|
||||
draw = false
|
||||
|
||||
render () {
|
||||
let bgTileStyele = {
|
||||
backgroundImage: `url(${state.backgroundTileUrl})`,
|
||||
opacity: state.backgroundOpacity,
|
||||
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className='tile-image' style={{ position: '' }}>
|
||||
<div className='background-image transparent-tile-bg' />
|
||||
<div className='background-image' style={bgTileStyele} />
|
||||
<canvas
|
||||
className='background-image'
|
||||
width='256' height='256'
|
||||
className='tile-image'
|
||||
onMouseDown={(e) => this.handleMouseDown(e)}
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.ctx.lineWidth = 10
|
||||
this.ctx.lineJoin = 'round'
|
||||
this.ctx.lineCap = 'round'
|
||||
}
|
||||
|
||||
get ctx () {
|
||||
return this.canvas.getContext('2d')
|
||||
}
|
||||
@ -27,21 +64,72 @@ export class Scratchpad extends Component {
|
||||
return this.canvas.toDataURL()
|
||||
}
|
||||
|
||||
handleMouseDown (e) {
|
||||
this.draw = true
|
||||
this.ctx.beginPath()
|
||||
this.ctx.moveTo(e.layerX, e.layerY)
|
||||
getCoords (e) {
|
||||
// handle touchevents
|
||||
e = e.touches ? e.touches[0] : e
|
||||
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) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseUp (e) {
|
||||
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 {
|
||||
@observable coords = { x: 0, y: 0, z: 0 }
|
||||
@observable backgroundTileServerUrl = ''
|
||||
@observable backgroundOpacity = 0.5
|
||||
|
||||
setState (newState) {
|
||||
this.state = { ...this.state, ...newState }
|
||||
@computed
|
||||
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