Add database, settings, etc; Update templates; Modernize JS
This commit is contained in:
parent
247ed94362
commit
704d52805f
159
.gitignore
vendored
Normal file
159
.gitignore
vendored
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Local test DB
|
||||||
|
mapmakr.sqlite
|
@ -1,15 +1,70 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import Depends, FastAPI, HTTPException, Request, Response
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
|
from mapmakr.models import Marker, MarkerRead, MarkerCreate, MarkerUpdate, create_db_tables, \
|
||||||
|
get_session
|
||||||
|
from mapmakr.settings import settings
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).parent
|
ROOT = Path(__file__).parent
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.mount('/static', StaticFiles(directory='static'), name='static')
|
app.mount('/static', StaticFiles(directory=str(ROOT / 'static')), name='static')
|
||||||
|
templates = Jinja2Templates(directory=str(ROOT / 'templates'))
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event('startup')
|
||||||
|
def on_startup():
|
||||||
|
create_db_tables()
|
||||||
|
|
||||||
|
|
||||||
@app.get('/', response_class=HTMLResponse)
|
@app.get('/', response_class=HTMLResponse)
|
||||||
async def index():
|
async def index(request: Request):
|
||||||
content = (ROOT / 'templates' / 'index.html').open().read()
|
return templates.TemplateResponse('index.html', {'request': request,
|
||||||
return HTMLResponse(content=content, status_code=200)
|
'site_title': settings.site_title})
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/marker', tags=['markers'], response_model=MarkerRead)
|
||||||
|
async def create_marker(marker: MarkerCreate, session: Session = Depends(get_session)) -> Marker:
|
||||||
|
db_marker = Marker.from_orm(marker)
|
||||||
|
session.add(db_marker)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(db_marker)
|
||||||
|
return db_marker
|
||||||
|
|
||||||
|
|
||||||
|
@app.patch('/marker/{marker_id}', tags=['markers'], response_model=MarkerRead)
|
||||||
|
async def update_marker(marker_id: int, marker: MarkerUpdate,
|
||||||
|
session: Session = Depends(get_session)) -> Marker:
|
||||||
|
db_marker = session.get(Marker, marker_id)
|
||||||
|
if not db_marker:
|
||||||
|
raise HTTPException(status_code=404, detail='Marker not found')
|
||||||
|
marker_data = marker.dict(exclude_unset=True)
|
||||||
|
for key, value in marker_data.items():
|
||||||
|
setattr(db_marker, key, value)
|
||||||
|
session.add(db_marker)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(db_marker)
|
||||||
|
return db_marker
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/marker', tags=['markers'], response_model=List[MarkerRead])
|
||||||
|
async def read_markers(session: Session = Depends(get_session)) -> list[MarkerRead]:
|
||||||
|
markers = session.exec(select(Marker)).all()
|
||||||
|
return markers
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete('/marker/{marker_id}', tags=['markers'])
|
||||||
|
async def delete_marker(marker_id: int, session: Session = Depends(get_session)):
|
||||||
|
db_marker = session.get(Marker, marker_id)
|
||||||
|
if not db_marker:
|
||||||
|
raise HTTPException(status_code=404, detail='Marker not found')
|
||||||
|
session.delete(db_marker)
|
||||||
|
session.commit()
|
||||||
|
return Response(status_code=200)
|
||||||
|
12
mapmakr/database.py
Normal file
12
mapmakr/database.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from sqlmodel import create_engine
|
||||||
|
|
||||||
|
from mapmakr.settings import settings
|
||||||
|
|
||||||
|
_engine = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_engine():
|
||||||
|
global _engine
|
||||||
|
if not _engine:
|
||||||
|
_engine = create_engine(settings.database_uri, echo=settings.database_echo)
|
||||||
|
return _engine
|
43
mapmakr/models.py
Normal file
43
mapmakr/models.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
from decimal import Decimal
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
from sqlalchemy.schema import Column
|
||||||
|
from sqlalchemy.types import JSON
|
||||||
|
from sqlmodel import Field, SQLModel, Session
|
||||||
|
|
||||||
|
from mapmakr.database import get_engine
|
||||||
|
|
||||||
|
|
||||||
|
class MarkerBase(SQLModel):
|
||||||
|
name: str
|
||||||
|
longitude: Decimal = Field(default=0)
|
||||||
|
latitude: Decimal = Field(default=0)
|
||||||
|
options: Dict[str, Any] = Field(sa_column=Column(JSON))
|
||||||
|
|
||||||
|
|
||||||
|
class Marker(MarkerBase, table=True): # type: ignore[call-arg]
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class MarkerCreate(MarkerBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MarkerRead(MarkerBase):
|
||||||
|
id: int
|
||||||
|
|
||||||
|
|
||||||
|
class MarkerUpdate(SQLModel):
|
||||||
|
name: Optional[str] = None
|
||||||
|
longitude: Optional[Decimal] = None
|
||||||
|
latitude: Optional[Decimal] = None
|
||||||
|
options: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
|
|
||||||
|
def create_db_tables():
|
||||||
|
SQLModel.metadata.create_all(get_engine())
|
||||||
|
|
||||||
|
|
||||||
|
def get_session():
|
||||||
|
with Session(get_engine()) as session:
|
||||||
|
yield session
|
10
mapmakr/settings.py
Normal file
10
mapmakr/settings.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from pydantic import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
database_uri: str = 'sqlite:///mapmakr.sqlite'
|
||||||
|
database_echo: bool = False
|
||||||
|
site_title: str = 'mapmakr'
|
||||||
|
|
||||||
|
|
||||||
|
settings = Settings()
|
@ -1,172 +1,136 @@
|
|||||||
/* */
|
let map, drawnItems, drawControl;
|
||||||
|
|
||||||
|
|
||||||
var map;
|
function reload_map() {
|
||||||
var drawnItems;
|
fetch("/marker").then(response => response.json()).then(markers => {
|
||||||
var statsControl;
|
// const map = window.map;
|
||||||
var drawControl;
|
drawnItems.clearLayers();
|
||||||
|
markers.forEach(marker => {
|
||||||
|
let mapMarker = L.marker({"lat": marker.latitude, "lng": marker.longitude});
|
||||||
// http://stackoverflow.com/a/18324384/661150
|
mapMarker.options["id"] = marker.id;
|
||||||
function callAjax(url, callback)
|
mapMarker.options["title"] = marker.name;
|
||||||
{
|
for (const opt in marker.options) {
|
||||||
var xmlhttp;
|
mapMarker.options[opt] = marker.options[opt];
|
||||||
// compatible with IE7+, Firefox, Chrome, Opera, Safari
|
}
|
||||||
xmlhttp = new XMLHttpRequest();
|
mapMarker.bindLabel(marker.name, {noHide: true});
|
||||||
xmlhttp.onreadystatechange = function(){
|
mapMarker.bindPopup(marker.name);
|
||||||
if (xmlhttp.readyState == 4 && xmlhttp.status == 200){
|
drawnItems.addLayer(mapMarker);
|
||||||
callback(xmlhttp.responseText);
|
});
|
||||||
}
|
if (!drawControl) {
|
||||||
}
|
|
||||||
xmlhttp.open("GET", url, true);
|
|
||||||
xmlhttp.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function gitmap_reload() {
|
|
||||||
callAjax("read.cgi", (function(req) {
|
|
||||||
var map = window.map;
|
|
||||||
drawnItems.clearLayers();
|
|
||||||
|
|
||||||
var objects = JSON.parse(req).objects;
|
|
||||||
var num_objects = objects.length;
|
|
||||||
for (var i=0; i<num_objects; i++) {
|
|
||||||
var gitmap_file = objects[i].file;
|
|
||||||
var gitmap_object = objects[i].contents;
|
|
||||||
var marker = L.marker(gitmap_object.location);
|
|
||||||
for (var opt in Object.keys(gitmap_object.options)) {
|
|
||||||
marker.options[opt] = gitmap_object.options[opt];
|
|
||||||
}
|
|
||||||
var popup = '';
|
|
||||||
if (gitmap_file.match(/^office/i)) {
|
|
||||||
marker.options.icon = L.AwesomeMarkers.icon({icon: 'building', prefix: 'fa', markerColor: 'red'});
|
|
||||||
popup = gitmap_file;
|
|
||||||
} else {
|
|
||||||
marker.options.icon = L.AwesomeMarkers.icon({icon: 'person', prefix: 'ion'});
|
|
||||||
text1 = '<a href="http://rover.redhat.com/people/profile/'+gitmap_file+'" target="_blank">roster</a>';
|
|
||||||
text2 = '';
|
|
||||||
popup = gitmap_file+' '+text1+' '+text2;
|
|
||||||
}
|
|
||||||
marker.options.id=gitmap_file;
|
|
||||||
marker.bindLabel(gitmap_file, { noHide: true });
|
|
||||||
marker.bindPopup(popup);
|
|
||||||
drawnItems.addLayer(marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawControl == null) {
|
|
||||||
// add drawControl only after some drawnItems exist, so
|
// add drawControl only after some drawnItems exist, so
|
||||||
// it is not shown disabled
|
// it is not shown disabled
|
||||||
drawControl = new L.Control.Draw({
|
drawControl = new L.Control.Draw({
|
||||||
draw: {
|
draw: {
|
||||||
position: 'topleft',
|
position: 'topleft',
|
||||||
polygon: false,
|
polygon: false,
|
||||||
rectangle: false,
|
rectangle: false,
|
||||||
polyline: false,
|
polyline: false,
|
||||||
circle: false
|
circle: false
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
featureGroup: drawnItems
|
featureGroup: drawnItems
|
||||||
}
|
|
||||||
});
|
|
||||||
map.addControl(drawControl);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
callAjax("summary.cgi", function(req) {
|
|
||||||
var sc = window.statsControl;
|
|
||||||
var leaflet = '<a href="http://leafletjs.com">Leaflet</a>';
|
|
||||||
sc.setPrefix (req + ' | ' + leaflet);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function gitmap_setup() {
|
|
||||||
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
||||||
osmAttrib = '© <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
|
||||||
osm = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib});
|
|
||||||
|
|
||||||
map = new L.Map('map', {layers: [osm], attributionControl: false, center: new L.LatLng(0, 0), zoom: 2 });
|
|
||||||
|
|
||||||
drawnItems = new L.MarkerClusterGroup({maxClusterRadius: 45}); // new L.FeatureGroup();
|
|
||||||
map.addLayer(drawnItems);
|
|
||||||
gitmap_reload();
|
|
||||||
|
|
||||||
// drawcontrol formerly added here
|
|
||||||
|
|
||||||
var refreshButton = new L.easyButton('icon ion-refresh', function() {gitmap_reload();});
|
|
||||||
refreshButton.button.style.fontSize = '18px';
|
|
||||||
map.addControl (refreshButton);
|
|
||||||
|
|
||||||
var helpButton = new L.easyButton('icon ion-help-circled', function() {open("about.html");});
|
|
||||||
helpButton.button.style.fontSize = '18px';
|
|
||||||
map.addControl (helpButton);
|
|
||||||
|
|
||||||
var controlSearch = new L.Control.Search({layer: drawnItems,
|
|
||||||
propertyName: 'id',
|
|
||||||
initial: false,
|
|
||||||
zoom: 12});
|
|
||||||
map.addControl( controlSearch );
|
|
||||||
|
|
||||||
statsControl = new L.control.attribution({position:'bottomright', prefix:''});
|
|
||||||
map.addControl(statsControl);
|
|
||||||
|
|
||||||
scaleControl = new L.control.scale();
|
|
||||||
map.addControl(scaleControl);
|
|
||||||
|
|
||||||
terminator = new L.terminator();
|
|
||||||
terminator.options.opacity=0.2;
|
|
||||||
terminator.options.fillOpacity=0.2;
|
|
||||||
terminator.addTo(map);
|
|
||||||
setInterval(function() {updateTerminator(terminator);}, 60000); // 1 minute
|
|
||||||
function updateTerminator(t) {
|
|
||||||
var t2 = L.terminator();
|
|
||||||
t.setLatLngs(t2.getLatLngs());
|
|
||||||
t.redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
// called after new marker is created
|
|
||||||
map.on('draw:created', function (e) {
|
|
||||||
var type = e.layerType,
|
|
||||||
layer = e.layer;
|
|
||||||
if (type !== 'marker') return;
|
|
||||||
|
|
||||||
var userid = prompt("Marker name (e.g., kerberos userid):", "");
|
|
||||||
if (userid != null) {
|
|
||||||
layer.options.id=userid;
|
|
||||||
layer.bindPopup(userid);
|
|
||||||
drawnItems.addLayer(layer);
|
|
||||||
callAjax("write.cgi?op=new"+
|
|
||||||
"&id="+encodeURIComponent(userid)+
|
|
||||||
"&location="+encodeURIComponent(JSON.stringify(layer.getLatLng()))+
|
|
||||||
"&options="+encodeURIComponent("{}"),
|
|
||||||
function(req){alert("response:\n"+req);
|
|
||||||
gitmap_reload();});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.log(map);
|
||||||
map.on('draw:edited', function (e) {
|
console.log(drawControl);
|
||||||
e.layers.eachLayer(function (layer) {
|
map.addControl(drawControl);
|
||||||
var type = layer.layerType;
|
}
|
||||||
var userid = layer.options.id;
|
});
|
||||||
callAjax("write.cgi?op=edit"+
|
}
|
||||||
"&id="+encodeURIComponent(userid)+
|
|
||||||
"&location="+encodeURIComponent(JSON.stringify(layer.getLatLng()))+
|
function setup_map() {
|
||||||
"&options="+encodeURIComponent("{}"),
|
let osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
function(req){alert("response:\n"+req);
|
osmAttrib = '© <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||||
gitmap_reload();});
|
osm = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib});
|
||||||
});
|
|
||||||
});
|
map = new L.Map('mapmakr', {layers: [osm], attributionControl: false, center: new L.LatLng(0, 0), zoom: 2 });
|
||||||
|
|
||||||
map.on('draw:deleted', function (e) {
|
drawnItems = new L.MarkerClusterGroup({maxClusterRadius: 45});
|
||||||
e.layers.eachLayer(function (layer) {
|
map.addLayer(drawnItems);
|
||||||
var type = layer.layerType;
|
reload_map();
|
||||||
var userid = layer.options.id;
|
|
||||||
callAjax("write.cgi?op=delete"+
|
const refreshButton = new L.easyButton('icon ion-refresh', function() {reload_map();});
|
||||||
"&id="+encodeURIComponent(userid),
|
refreshButton.button.style.fontSize = '18px';
|
||||||
function(req){alert("response:\n"+req);
|
map.addControl(refreshButton);
|
||||||
gitmap_reload();});
|
|
||||||
});
|
const helpButton = new L.easyButton('icon ion-help-circled', function() {open("about.html");});
|
||||||
});
|
helpButton.button.style.fontSize = '18px';
|
||||||
|
map.addControl(helpButton);
|
||||||
|
|
||||||
|
const controlSearch = new L.Control.Search({layer: drawnItems, propertyName: 'id', initial: false, zoom: 12});
|
||||||
|
map.addControl(controlSearch);
|
||||||
|
|
||||||
|
statsControl = new L.control.attribution({position:'bottomright', prefix:''});
|
||||||
|
map.addControl(statsControl);
|
||||||
|
|
||||||
|
scaleControl = new L.control.scale();
|
||||||
|
map.addControl(scaleControl);
|
||||||
|
|
||||||
|
terminator = new L.terminator();
|
||||||
|
terminator.options.opacity = 0.2;
|
||||||
|
terminator.options.fillOpacity = 0.2;
|
||||||
|
terminator.addTo(map);
|
||||||
|
setInterval(() => updateTerminator(terminator), 60000); // 1 minute
|
||||||
|
|
||||||
|
function updateTerminator(t) {
|
||||||
|
var t2 = L.terminator();
|
||||||
|
t.setLatLngs(t2.getLatLngs());
|
||||||
|
t.redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
// called after new marker is created
|
||||||
|
map.on('draw:created', function (e) {
|
||||||
|
let type = e.layerType, layer = e.layer;
|
||||||
|
if (type !== 'marker') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = prompt("Marker name:", "");
|
||||||
|
if (name) {
|
||||||
|
layer.options.id = null;
|
||||||
|
layer.options.title = name;
|
||||||
|
layer.bindPopup(name);
|
||||||
|
drawnItems.addLayer(layer);
|
||||||
|
let requestOptions = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"name": name,
|
||||||
|
"latitude": layer.getLatLng()["lat"],
|
||||||
|
"longitude": layer.getLatLng()["lng"],
|
||||||
|
"options": {}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
fetch("/marker", requestOptions).then(() => reload_map());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
map.on('draw:edited', function (e) {
|
||||||
|
e.layers.eachLayer(layer => {
|
||||||
|
let type = layer.layerType, id = layer.options.id;
|
||||||
|
let requestOptions = {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"name": layer.options.name,
|
||||||
|
"latitude": layer.getLatLng()["lat"],
|
||||||
|
"longitude": layer.getLatLng()["lng"]
|
||||||
|
})
|
||||||
|
};
|
||||||
|
fetch("/marker/" + id, requestOptions).then(() => reload_map());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
map.on('draw:deleted', function (e) {
|
||||||
|
e.layers.eachLayer(layer => {
|
||||||
|
let type = layer.layerType, id = layer.options.id;
|
||||||
|
fetch("/marker/" + id, {method: "DELETE"}).then(() => reload_map());
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<html><head><title>about gitmap</title></head>
|
<html><head><title>about mapmakr</title></head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h1>about gitmap</h1>
|
<h1>about mapmakr</h1>
|
||||||
|
|
||||||
<p>gitmap is a simple, small, 100% free/open-source, collaborative map
|
<p>mapmakr is a simple, small, 100% free/open-source, collaborative map
|
||||||
editor that runs mostly in a web browser. It integrates community
|
editor that runs mostly in a web browser. It integrates community
|
||||||
FOSS projects and data sources.</p>
|
FOSS projects and data sources.</p>
|
||||||
|
|
||||||
<b><a href="index.html">run gitmap</a></b>
|
<b><a href="index.html">run mapmakr</a></b>
|
||||||
|
|
||||||
<h1>FAQ</h1>
|
<h1>FAQ</h1>
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ installation is for locating Red Hat remotees / offices. Other installations
|
|||||||
may store whatever they like.</p>
|
may store whatever they like.</p>
|
||||||
|
|
||||||
<h2>Who?</h2>
|
<h2>Who?</h2>
|
||||||
<p><a href="mailto:fche@redhat.com">fche</a> (gitmap integration scripts),
|
<p><a href="mailto:fche@redhat.com">fche</a> (mapmakr integration scripts),
|
||||||
<a href="http://www.openstreetmap.org">Open Streetmap</a> (background map),
|
<a href="http://www.openstreetmap.org">Open Streetmap</a> (background map),
|
||||||
<a href="http://www.leafletjs.com">Leaflet</a> (map rendering javascript library + plugins),
|
<a href="http://www.leafletjs.com">Leaflet</a> (map rendering javascript library + plugins),
|
||||||
and others.</p>
|
and others.</p>
|
||||||
@ -55,7 +55,7 @@ Click on doomed markers. Click on "save" or "cancel".</p>
|
|||||||
|
|
||||||
<h2>How does it work?</h2>
|
<h2>How does it work?</h2>
|
||||||
|
|
||||||
<p><a href="https://gitlab.cee.redhat.com/fche/gitmap">Check it out.</a></p>
|
<p><a href="https://gitlab.cee.redhat.com/fche/mapmakr">Check it out.</a></p>
|
||||||
<p>The front-end consists of very small HTML + JavaScript files that
|
<p>The front-end consists of very small HTML + JavaScript files that
|
||||||
load Leaflet and OSM data from the Internet into your browser. The
|
load Leaflet and OSM data from the Internet into your browser. The
|
||||||
front-end also loads marker data from our own servers via HTTP CGI.
|
front-end also loads marker data from our own servers via HTTP CGI.
|
||||||
@ -77,8 +77,8 @@ the job, and easily.</p>
|
|||||||
|
|
||||||
<h2>Where to send patches?</h2>
|
<h2>Where to send patches?</h2>
|
||||||
|
|
||||||
<p>Find gitmap sources
|
<p>Find mapmakr sources
|
||||||
at <tt>https://gitlab.cee.redhat.com/fche/gitmap.git</tt>.
|
at <tt>https://gitlab.cee.redhat.com/fche/mapmakr.git</tt>.
|
||||||
One or two third-party javascript libraries are stored there; others
|
One or two third-party javascript libraries are stored there; others
|
||||||
are served from upstream projects' external CDNs. Feel free to fork /
|
are served from upstream projects' external CDNs. Feel free to fork /
|
||||||
experiment / deploy. Send patches to <tt>fche</tt> if desired.</p>
|
experiment / deploy. Send patches to <tt>fche</tt> if desired.</p>
|
||||||
@ -91,7 +91,7 @@ experiment / deploy. Send patches to <tt>fche</tt> if desired.</p>
|
|||||||
</ol>
|
</ol>
|
||||||
Patches are welcome!</p>
|
Patches are welcome!</p>
|
||||||
|
|
||||||
<p>Please note that the version of the gitmap tree
|
<p>Please note that the version of the mapmakr tree
|
||||||
in <tt>gitlab.cee</tt> contains snapshots of the real live
|
in <tt>gitlab.cee</tt> contains snapshots of the real live
|
||||||
personal data RH folks have chosen to share. It is obviously
|
personal data RH folks have chosen to share. It is obviously
|
||||||
confidential.</p>
|
confidential.</p>
|
||||||
|
@ -1,30 +1,29 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>gitmap editor</title>
|
<title>{{site_title}}</title>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.5/leaflet.css" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.5/leaflet.css" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.css" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.css" />
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css"/>
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css"/>
|
||||||
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css"/>
|
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css"/>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css" />
|
||||||
<link rel="stylesheet" href="3rdparty/leaflet-search.css" />
|
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/leaflet-search.css') }}" />
|
||||||
<link rel="stylesheet" href="3rdparty/leaflet-easybutton.css" />
|
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/leaflet-easybutton.css') }}" />
|
||||||
<link rel="stylesheet" href="3rdparty/leaflet-label.css" />
|
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/leaflet-label.css') }}" />
|
||||||
<link rel="stylesheet" href="3rdparty/MarkerCluster.css"/>
|
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/MarkerCluster.css') }}"/>
|
||||||
<link rel="stylesheet" href="3rdparty/MarkerCluster.Default.css"/>
|
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/MarkerCluster.Default.css') }}"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="mapmakr" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.5/leaflet.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.5/leaflet.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.min.js"></script>
|
||||||
<script src="3rdparty/leaflet-search.js"></script>
|
<script src="{{ url_for('static', path='/3rdparty/leaflet-search.js') }}"></script>
|
||||||
<script src="3rdparty/leaflet-easybutton.js"></script>
|
<script src="{{ url_for('static', path='/3rdparty/leaflet-easybutton.js') }}"></script>
|
||||||
<script src="3rdparty/leaflet-label.js"></script>
|
<script src="{{ url_for('static', path='/3rdparty/leaflet-label.js') }}"></script>
|
||||||
<script src="3rdparty/L.Terminator.js"></script>
|
<script src="{{ url_for('static', path='/3rdparty/L.Terminator.js') }}"></script>
|
||||||
<script src="3rdparty/leaflet.markercluster.js"></script>
|
<script src="{{ url_for('static', path='/3rdparty/leaflet.markercluster.js') }}"></script>
|
||||||
<script src="mapmakr.js"></script>
|
<script src="{{ url_for('static', path='/js/mapmakr.js') }}"></script>
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="map" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>
|
|
||||||
<script>setup_map();</script>
|
<script>setup_map();</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
|||||||
[project]
|
[project]
|
||||||
name = "mapmakr"
|
name = "mapmakr"
|
||||||
description = 'An interactive map where you can place your own markers'
|
description = 'An interactive map where you can place your own markers'
|
||||||
readme = "README.md"
|
readme = "README.rst"
|
||||||
requires-python = ">=3.7"
|
requires-python = ">=3.7"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
keywords = []
|
keywords = []
|
||||||
@ -26,7 +26,8 @@ classifiers = [
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"FastAPI",
|
"FastAPI",
|
||||||
"Jinja2",
|
"Jinja2",
|
||||||
"uvicorn[standard]"
|
"uvicorn[standard]",
|
||||||
|
"SQLModel",
|
||||||
]
|
]
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user