feat: fontnik和sprite工具修改,osm-bright-style使用

This commit is contained in:
zhouxhere 2025-06-12 18:28:40 +08:00
parent c7b41ff7aa
commit a4f49c7ee0
12 changed files with 576 additions and 211 deletions

View File

@ -64,6 +64,10 @@ func NewAPI(e *echo.Echo, s *store.Store) {
api.GET("/swagger", func(c echo.Context) error { api.GET("/swagger", func(c echo.Context) error {
return c.Redirect(http.StatusMovedPermanently, "/swagger/index.html") return c.Redirect(http.StatusMovedPermanently, "/swagger/index.html")
}) })
// style
api.Static("/style", "./style")
api.Register() api.Register()
for _, route := range api.Routes() { for _, route := range api.Routes() {

View File

@ -32,7 +32,7 @@ func testSprite() {
panic(err) panic(err)
} }
spriteBuilder.Build() spriteBuilder.Build("icons")
} }
func testFont() { func testFont() {
@ -46,7 +46,9 @@ func testFont() {
panic(err) panic(err)
} }
fonts.ToPBF() fonts.Build("Noto Sans Bold")
fonts.Build("Noto Sans Italic")
fonts.Build("Noto Sans Regular")
} }
func testTiles() { func testTiles() {

View File

@ -4,9 +4,11 @@ import (
"fmt" "fmt"
"image" "image"
"image/draw" "image/draw"
"log/slog"
"math" "math"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"git.zhouxhere.com/zhouxhere/maptile/protobuf" "git.zhouxhere.com/zhouxhere/maptile/protobuf"
@ -38,8 +40,10 @@ type FontFace struct {
} }
type Fontnik struct { type Fontnik struct {
outPath string outPath string
FontFaces []*FontFace // Name string
// FontFaces []*FontFace
FontMaps map[string][]*FontFace
} }
func NewFontnik(fontPath, outPath string) (*Fontnik, error) { func NewFontnik(fontPath, outPath string) (*Fontnik, error) {
@ -58,110 +62,122 @@ func NewFontnik(fontPath, outPath string) (*Fontnik, error) {
files, err := os.ReadDir(fontPath) files, err := os.ReadDir(fontPath)
if err != nil { if err != nil {
panic(err) return nil, fmt.Errorf("读取字体目录失败: %v", err)
} }
fontFaces := []*FontFace{} fontMap := make(map[string][]*FontFace)
for _, file := range files { for _, file := range files {
if file.IsDir() {
continue
}
filePath := filepath.Join(fontPath, file.Name()) filePath := filepath.Join(fontPath, file.Name())
if file.IsDir() {
if !strings.HasSuffix(file.Name(), ".ttf") && !strings.HasSuffix(file.Name(), ".otf") { faces := readFonts(filePath)
// return nil, errors.Errorf("font file not support %s", file.Name()) fontMap[file.Name()] = faces
continue continue
} }
fontBytes, err := os.ReadFile(filePath) fontFace, err := readFont(filePath)
if err != nil { if err != nil {
// return nil, fmt.Errorf("读取字体文件失败: %v", err) slog.Error("Failed to read font file %s: %v", filePath, err)
continue continue
} }
openFont, err := opentype.Parse(fontBytes) fileName := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))
if err != nil {
// return nil, fmt.Errorf("解析字体失败: %v", err)
continue
}
fontFamily, err := openFont.Name(&sfnt.Buffer{}, sfnt.NameIDFamily) fontMap[fileName] = append(fontMap[fileName], fontFace)
if err != nil {
return nil, err
}
fontSubFamily, err := openFont.Name(&sfnt.Buffer{}, sfnt.NameIDSubfamily)
if err != nil {
return nil, err
}
fontFamily = fmt.Sprintf("%s %s", fontFamily, fontSubFamily)
face, err := opentype.NewFace(openFont, &opentype.FaceOptions{
Size: DefaultFontSize,
DPI: 72,
Hinting: font.HintingFull,
})
if err != nil {
return nil, err
}
metrics := face.Metrics()
// fontDesignedHeight := metrics.Ascent.Floor() + metrics.Descent.Floor()
// fixed := int(math.Round(float64(metrics.Height.Floor()-fontDesignedHeight)/2)) + 1
fontFaces = append(fontFaces, &FontFace{
Name: fontFamily,
Font: face,
// yStart: metrics.Height.Floor() + metrics.Descent.Floor() + fixed,
yStart: metrics.Ascent.Floor(),
// maxCode: openFont.NumGlyphs(),
maxCode: DefaultMaxCode,
})
} }
return &Fontnik{ return &Fontnik{
outPath: filepath.Join(filepath.Dir(basePath), outPath), outPath: filepath.Join(filepath.Dir(basePath), outPath),
FontFaces: fontFaces, // FontFaces: fontFaces,
FontMaps: fontMap,
}, nil }, nil
} }
func (f *Fontnik) ToPBF() { func (f *Fontnik) Build(name string) error {
if _, ok := f.FontMaps[name]; !ok {
return fmt.Errorf("font %s not found", name)
}
if _, err := os.Stat(f.outPath); err != nil { path := filepath.Join(f.outPath, name)
err = os.MkdirAll(f.outPath, 0644) if _, err := os.Stat(path); err != nil {
err = os.MkdirAll(path, 0644)
if err != nil { if err != nil {
panic(err) return err
} }
} }
for _, face := range f.FontFaces { var errs []error
if _, err := os.Stat(filepath.Join(f.outPath, face.Name)); err != nil { for start := 0; start <= DefaultMaxCode; start += DefaultBlockSize {
err = os.MkdirAll(filepath.Join(f.outPath, face.Name), 0644)
if err != nil { endCode := min(start+DefaultBlockSize-1, DefaultMaxCode)
panic(err)
stackRange := fmt.Sprintf("%d-%d", start, endCode)
fontstack := &protobuf.Fontstack{
Name: &name,
Range: &stackRange,
Glyphs: []*protobuf.Glyph{},
}
for _, face := range f.FontMaps[name] {
if face.maxCode < start {
continue
} }
tmpGlyphs, err := face.ProcessRange(start, endCode)
if err != nil {
errs = append(errs, err)
slog.Error("Failed to process font %s range: %v", name, err)
continue
}
fontstack.Glyphs = append(fontstack.Glyphs, tmpGlyphs...)
} }
fmt.Println("Font:", face.Name, "MaxCode:", face.maxCode) if len(f.FontMaps[name]) > 1 {
sort.Slice(fontstack.Glyphs, func(i, j int) bool {
return fontstack.Glyphs[i].GetId() < fontstack.Glyphs[j].GetId()
})
}
for start := 0; start <= int(face.maxCode); start += DefaultBlockSize { if err := f.SaveStack(fontstack); err != nil {
errs = append(errs, err)
endCode := min(start+DefaultBlockSize-1, face.maxCode) slog.Error("Failed to save font %s stack: %v", name, err)
continue
stack, err := face.ProcessRange(start, endCode)
if err != nil {
return
}
if err := f.SaveStack(stack); err != nil {
fmt.Println(err)
return
}
} }
} }
if len(errs) > 0 {
return fmt.Errorf("build font %s failed: %v", name, errs)
}
return nil
// for _, face := range f.FontFaces {
// if _, err := os.Stat(filepath.Join(f.outPath, face.Name)); err != nil {
// err = os.MkdirAll(filepath.Join(f.outPath, face.Name), 0644)
// if err != nil {
// panic(err)
// }
// }
// fmt.Println("Font:", face.Name, "MaxCode:", face.maxCode)
// for start := 0; start <= int(face.maxCode); start += DefaultBlockSize {
// endCode := min(start+DefaultBlockSize-1, face.maxCode)
// stack, err := face.ProcessRange(start, endCode)
// if err != nil {
// return
// }
// if err := f.SaveStack(stack); err != nil {
// fmt.Println(err)
// return
// }
// }
// }
} }
func (f *Fontnik) SaveStack(stack *protobuf.Fontstack) error { func (f *Fontnik) SaveStack(stack *protobuf.Fontstack) error {
@ -178,24 +194,26 @@ func (f *Fontnik) SaveStack(stack *protobuf.Fontstack) error {
return os.WriteFile(filename, data, 0644) return os.WriteFile(filename, data, 0644)
} }
func (ff *FontFace) ProcessRange(start, end int) (*protobuf.Fontstack, error) { func (ff *FontFace) ProcessRange(start, end int) ([]*protobuf.Glyph, error) {
fontRange := fmt.Sprintf("%d-%d", start, end) // fontRange := fmt.Sprintf("%d-%d", start, end)
stack := &protobuf.Fontstack{ // stack := &protobuf.Fontstack{
Name: &ff.Name, // Name: &ff.Name,
Range: &fontRange, // Range: &fontRange,
Glyphs: []*protobuf.Glyph{}, // Glyphs: []*protobuf.Glyph{},
} // }
glyphs := []*protobuf.Glyph{}
for code := start; code <= end; code++ { for code := start; code <= end; code++ {
glyph := ff.RenderGlyph(rune(code)) glyph := ff.RenderGlyph(rune(code))
if glyph != nil { if glyph != nil {
stack.Glyphs = append(stack.Glyphs, glyph) // stack.Glyphs = append(stack.Glyphs, glyph)
glyphs = append(glyphs, glyph)
} }
} }
return stack, nil return glyphs, nil
} }
func (ff *FontFace) RenderGlyph(code rune) *protobuf.Glyph { func (ff *FontFace) RenderGlyph(code rune) *protobuf.Glyph {
@ -367,3 +385,82 @@ func edt1d(f []float64, d []float64, v []float64, z []float64, n int) {
d[q] = (float64(q)-v[k])*(float64(q)-v[k]) + f[int(v[k])] d[q] = (float64(q)-v[k])*(float64(q)-v[k]) + f[int(v[k])]
} }
} }
func readFonts(path string) []*FontFace {
var files []string
ttfFiles, err := filepath.Glob(path + "/*.ttf")
if err != nil {
slog.Error("Failed to read ttf files from path %s: %v", path, err)
}
files = append(files, ttfFiles...)
otfFiles, err := filepath.Glob(path + "/*.otf")
if err != nil {
slog.Error("Failed to read otf files from path %s: %v", path, err)
}
files = append(files, otfFiles...)
var fontFaces []*FontFace
for _, file := range files {
fontFace, err := readFont(file)
if err != nil {
slog.Error("Failed to read font file %s: %v", file, err)
continue
}
fontFaces = append(fontFaces, fontFace)
}
return fontFaces
}
func readFont(path string) (*FontFace, error) {
if !strings.HasSuffix(path, ".ttf") && !strings.HasSuffix(path, ".otf") {
return nil, fmt.Errorf("unsupported font format: %s", path)
}
fontBytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
openFont, err := opentype.Parse(fontBytes)
if err != nil {
return nil, err
}
fontFamily, err := openFont.Name(&sfnt.Buffer{}, sfnt.NameIDFamily)
if err != nil {
return nil, err
}
fontSubFamily, err := openFont.Name(&sfnt.Buffer{}, sfnt.NameIDSubfamily)
if err != nil {
return nil, err
}
fontFamily = fmt.Sprintf("%s %s", fontFamily, fontSubFamily)
face, err := opentype.NewFace(openFont, &opentype.FaceOptions{
Size: DefaultFontSize,
DPI: 72,
Hinting: font.HintingFull,
})
if err != nil {
return nil, err
}
metrics := face.Metrics()
// fontDesignedHeight := metrics.Ascent.Floor() + metrics.Descent.Floor()
// fixed := int(math.Round(float64(metrics.Height.Floor()-fontDesignedHeight)/2)) + 1
return &FontFace{
Name: fontFamily,
Font: face,
// yStart: metrics.Height.Floor() + metrics.Descent.Floor() + fixed,
yStart: metrics.Ascent.Floor(),
// maxCode: openFont.NumGlyphs(),
maxCode: DefaultMaxCode,
}, nil
}

View File

@ -63,7 +63,7 @@ func NewSpriteBuilder(iconPath string, outPath string) (*SpriteBuilder, error) {
files, err := os.ReadDir(iconPath) files, err := os.ReadDir(iconPath)
if err != nil { if err != nil {
panic(err) return nil, err
} }
sb := &SpriteBuilder{ sb := &SpriteBuilder{
@ -113,27 +113,58 @@ func NewSpriteBuilder(iconPath string, outPath string) (*SpriteBuilder, error) {
return sb, nil return sb, nil
} }
func (sb *SpriteBuilder) Build() error { func (sb *SpriteBuilder) Build(name string) error {
for _, sprite := range sb.sprites { // 查找name为name的sprite
var sprite *Sprite
if _, err := os.Stat(filepath.Join(sb.outPath, sprite.name)); err != nil { for _, s := range sb.sprites {
err = os.MkdirAll(filepath.Join(sb.outPath, sprite.name), 0644) if s.name == name {
if err != nil { sprite = &s
return err break
}
} }
}
sprite.Fit() if sprite == nil {
return fmt.Errorf("sprite %s not found", name)
}
if err := sprite.Save(sb.outPath, 1); err != nil { if _, err := os.Stat(filepath.Join(sb.outPath, sprite.name)); err != nil {
return err err = os.MkdirAll(filepath.Join(sb.outPath, sprite.name), 0644)
} if err != nil {
if err := sprite.Save(sb.outPath, 2); err != nil {
return err return err
} }
} }
sprite.Fit()
if err := sprite.Save(sb.outPath, 1); err != nil {
return err
}
if err := sprite.Save(sb.outPath, 2); err != nil {
return err
}
return nil return nil
// for _, sprite := range sb.sprites {
// if _, err := os.Stat(filepath.Join(sb.outPath, sprite.name)); err != nil {
// err = os.MkdirAll(filepath.Join(sb.outPath, sprite.name), 0644)
// if err != nil {
// return err
// }
// }
// sprite.Fit()
// if err := sprite.Save(sb.outPath, 1); err != nil {
// return err
// }
// if err := sprite.Save(sb.outPath, 2); err != nil {
// return err
// }
// }
// return nil
} }
func (s *Sprite) Fit() { func (s *Sprite) Fit() {

View File

@ -0,0 +1,63 @@
<script setup lang="ts">
import { json } from "@codemirror/lang-json";
import {
defaultHighlightStyle,
syntaxHighlighting,
} from "@codemirror/language";
import { EditorState } from "@codemirror/state";
import { basicSetup, EditorView } from "codemirror";
const { codes } = defineProps<{ codes: object | undefined }>();
const emit = defineEmits(["update:codes"]);
const view = ref<EditorView>();
watchEffect(() => {
if (!view.value) return;
view.value.dispatch({
changes: {
from: 0,
to: view.value.state.doc.length,
insert: JSON.stringify(codes, null, 2),
},
});
});
onMounted(() => {
const state = EditorState.create({
doc: codes ? JSON.stringify(codes, null, 2) : "",
extensions: [
basicSetup,
syntaxHighlighting(defaultHighlightStyle),
json(),
EditorState.transactionFilter.of((tr) => {
if (tr.docChanged) {
// emit("update:codes", tr.newDoc.toJSON());
debouncedUpdate(JSON.parse(tr.newDoc.toString()));
}
return tr;
}),
],
});
view.value = new EditorView({
parent: document.getElementById("code") as HTMLElement,
state: state,
});
});
const debouncedUpdate = useDebounceFn((value: object) => {
emit("update:codes", value);
}, 1000 * 3);
</script>
<template>
<div id="code"></div>
</template>
<style scoped>
:global(.cm-editor) {
height: 100%;
}
</style>

View File

@ -0,0 +1,54 @@
<script setup lang="ts">
import type { StyleSpecification } from "maplibre-gl";
const { mapStyle } = defineProps<{
mapStyle: StyleSpecification & { id: string };
}>();
const emit = defineEmits<{
(e: "update:mapStyle", mapStyle: StyleSpecification & { id: string }): void;
}>();
const updateMapStyle = (mapStyle: StyleSpecification & { id: string }) => {
emit("update:mapStyle", mapStyle);
};
const sources = computed(() => {
return mapStyle.sources;
});
const layers = computed(() => {
return mapStyle.layers;
});
const glyphs = computed(() => {
return mapStyle.glyphs;
});
const sprite = computed(() => {
return mapStyle.sprite;
});
</script>
<template>
<div class="flex flex-col">
<div v-for="(source, name) in sources" class="flex flex-col">
<h4>{{ name.toString() }}-{{ source.type }}</h4>
<div class="flex flex-col" v-for="(value, key) in source">
<div class="flex">
<h6>{{ key }}:</h6>
<p>{{ value }}</p>
</div>
</div>
</div>
<div v-for="(layer, name) in layers" class="flex flex-col">
<h4>{{ name.toString() }}-{{ layer.type }}</h4>
<div class="flex flex-col" v-for="(value, key) in layer">
<div class="flex">
<h6>{{ key }}:</h6>
<p>{{ value }}</p>
</div>
</div>
</div>
</div>
</template>

View File

@ -4,6 +4,9 @@ import "maplibre-gl/dist/maplibre-gl.css";
import * as turf from "@turf/turf"; import * as turf from "@turf/turf";
import MapboxDraw from "@mapbox/mapbox-gl-draw"; import MapboxDraw from "@mapbox/mapbox-gl-draw";
import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css"; import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css";
import Code from "./code.vue";
import Editor from "./editor.vue";
import { StyleSpecification } from "maplibre-gl";
const mapContainer = useTemplateRef("mapContainer"); const mapContainer = useTemplateRef("mapContainer");
@ -24,12 +27,23 @@ MapboxDraw.lib.theme.find((x) => x.id === "gl-draw-lines").paint[
"line-dasharray" "line-dasharray"
] = [0.2, 2]; ] = [0.2, 2];
/**
* @type {Ref<StyleSpecification & {id string}>}
*/
const styleJSON = ref({
version: 8,
sources: {},
layers: [],
});
onMounted(async () => { onMounted(async () => {
styleJSON.value = await fetch("/style.json").then((res) => res.json());
if (!mapContainer.value) return; if (!mapContainer.value) return;
map.value = new maplibregl.Map({ map.value = new maplibregl.Map({
container: mapContainer.value, // container id container: mapContainer.value, // container id
// style: "https://demotiles.maplibre.org/style.json", // style URL // style: "https://demotiles.maplibre.org/style.json", // style URL
style: "/style.json", // style: "/style.json",
style: styleJSON.value,
center: [120.147376, 30.272934], // starting position [lng, lat] center: [120.147376, 30.272934], // starting position [lng, lat]
zoom: 7, // starting zoom zoom: 7, // starting zoom
}); });
@ -73,17 +87,17 @@ onUnmounted(() => {
const featureResult = await feature.list({ page: 1, size: 10 }); const featureResult = await feature.list({ page: 1, size: 10 });
const handleData = () => { const handleData = () => {
// if (featureResult.status.value !== "success") return; if (featureResult.status.value !== "success") return;
// let jsonData = turf.featureCollection( let jsonData = turf.featureCollection(
// featureResult.data.value.data.map((x) => featureResult.data.value.data.map((x) =>
// turf.feature(x.geometry, { turf.feature(x.geometry, {
// id: x.id, id: x.id,
// name: x.name, name: x.name,
// }) })
// ) )
// ); );
// map.value.addSource("data", { type: "geojson", data: jsonData }); map.value.addSource("data", { type: "geojson", data: jsonData });
// map.value.addLayer({ // map.value.addLayer({
// id: "data-symbol", // id: "data-symbol",
// type: "symbol", // type: "symbol",
@ -93,7 +107,6 @@ const handleData = () => {
// }, // },
// layout: { // layout: {
// "text-field": ["get", "name"], // "text-field": ["get", "name"],
// "text-font": ["Open Sans Regular", "Arial Unicode MS Regular"],
// "text-size": 12, // "text-size": 12,
// }, // },
// }); // });
@ -107,97 +120,26 @@ const handleData = () => {
// "fill-outline-color": "#FF0000", // 线 // "fill-outline-color": "#FF0000", // 线
// }, // },
// }); // });
};
// map.value.addSource("zhejiang-pm", { const handleCodesChange = (codes) => {
// type: "vector", if (!map.value) return;
// url: "/api/v1/pmtiles/zhejiang-pm", console.log(codes);
// // url: "http://localhost:8080/zhejiang-pm.json" map.value.setStyle(codes);
// });
// map.value.addLayer({
// id: "water",
// type: "fill",
// filter: ["==", "$type", "Polygon"],
// source: "zhejiang-pm",
// "source-layer": "water",
// paint: {
// "fill-color": "#80deea",
// },
// });
// map.value.addLayer({
// id: "water_river",
// type: "line",
// source: "zhejiang-pm",
// "source-layer": "water",
// minzoom: 9,
// filter: ["in", "kind", "river"],
// paint: {
// "line-color": "#80deea",
// "line-width": [
// "interpolate",
// ["exponential", 1.6],
// ["zoom"],
// 9,
// 0,
// 9.5,
// 1,
// 18,
// 12,
// ],
// },
// });
// map.value.addLayer({
// id: "world_cities",
// type: "symbol",
// minzoom: 0,
// maxzoom: 6,
// source: "world_cities",
// "source-layer": "cities",
// layout: {
// "text-field": "{name}",
// // "text-font": ["Open Sans Regular", "Arial Unicode MS Regular"],
// "text-size": 12,
// },
// paint: {
// "text-color": "rgb(255,0,0)",
// },
// });
// map.value.addSource("china-shortbread", {
// type: "vector",
// // url: "/api/v1/mbtiles/china-shortbread",
// bounds: [73.41788, 14.27437, 134.8036, 53.65559],
// maxzoom: 14,
// minzoom: 0,
// tiles: [
// "http://localhost:8888/api/v1/mbtiles/china-shortbread/{z}/{x}/{y}",
// ],
// });
// map.value.addLayer({
// id: "china-shortbread",
// type: "symbol",
// bounds: [73.41788, 14.27437, 134.8036, 53.65559],
// maxzoom: 14,
// minzoom: 0,
// source: "china-shortbread",
// "source-layer": "place_labels",
// layout: {
// "text-field": "hello",
// // "text-font": ["Open Sans Regular", "Arial Unicode MS Regular"],
// "text-size": 12,
// },
// paint: {
// "text-color": "#000",
// },
// });
setTimeout(() => {
let styles = map.value.getStyle();
console.log(styles);
}, 1000);
}; };
</script> </script>
<template> <template>
<div ref="mapContainer" class="w-full h-full" /> <div class="w-full h-full flex justify-between">
<Code
class="h-full flex-[0_0_20%] overflow-hidden"
:codes="styleJSON"
@update:codes="handleCodesChange"
></Code>
<Editor
class="h-full flex-[0_0_20%] overflow-x-hidden overflow-y-auto"
:map-style="styleJSON"
></Editor>
<div ref="mapContainer" class="h-full flex-[0_0_50%]"></div>
</div>
</template> </template>

View File

@ -28,7 +28,7 @@ const route = useRoute();
</label> </label>
<div class="dropdown dropdown-end ml-auto mr-2"> <div class="dropdown dropdown-end ml-auto mr-2">
<div tabindex="0" role="button" class="avatar online placeholder sm-auto"> <div tabindex="0" role="button" class="avatar online placeholder sm-auto">
<div class="bg-neutral text-neutral-content w-8 rounded-full"> <div class="bg-neutral text-neutral-content w-8 rounded-full !flex items-center justify-center">
<span class="text-sm">AI</span> <span class="text-sm">AI</span>
</div> </div>
</div> </div>
@ -76,9 +76,9 @@ const route = useRoute();
<main <main
class="drawer-content flex-auto relative p-4 pb-0 overflow-hidden bg-base-100 flex flex-col" class="drawer-content flex-auto relative p-4 pb-0 overflow-hidden bg-base-100 flex flex-col"
> >
<Breadcrumbs /> <Breadcrumbs class="flex-none" />
<section class="flex-auto overflow-y-auto overflow-x-hidden"> <section class="flex-auto overflow-y-auto overflow-x-hidden">
<slot /> <slot></slot>
</section> </section>
<footer class="mt-auto w-full h-8 py-2 text-center text-xs"> <footer class="mt-auto w-full h-8 py-2 text-center text-xs">
&copy;powered by maptile &copy;powered by maptile

169
web/package-lock.json generated
View File

@ -7,9 +7,13 @@
"name": "nuxt-app", "name": "nuxt-app",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.11.1",
"@codemirror/state": "^6.5.2",
"@mapbox/mapbox-gl-draw": "^1.5.0", "@mapbox/mapbox-gl-draw": "^1.5.0",
"@turf/turf": "^7.2.0", "@turf/turf": "^7.2.0",
"@vueuse/nuxt": "^12.7.0", "@vueuse/nuxt": "^12.7.0",
"codemirror": "^6.0.1",
"maplibre-gl": "^5.1.1", "maplibre-gl": "^5.1.1",
"nuxt": "^3.16.2", "nuxt": "^3.16.2",
"vue": "latest", "vue": "latest",
@ -484,6 +488,97 @@
"node": ">=10.0.0" "node": ">=10.0.0"
} }
}, },
"node_modules/@codemirror/autocomplete": {
"version": "6.18.6",
"resolved": "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/commands": {
"version": "6.8.1",
"resolved": "https://registry.npmmirror.com/@codemirror/commands/-/commands-6.8.1.tgz",
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.27.0",
"@lezer/common": "^1.1.0"
}
},
"node_modules/@codemirror/lang-json": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@lezer/json": "^1.0.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.11.1",
"resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.11.1.tgz",
"integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
"@lezer/common": "^1.1.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/lint": {
"version": "6.8.5",
"resolved": "https://registry.npmmirror.com/@codemirror/lint/-/lint-6.8.5.tgz",
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.35.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search": {
"version": "6.5.11",
"resolved": "https://registry.npmmirror.com/@codemirror/search/-/search-6.5.11.tgz",
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/state": {
"version": "6.5.2",
"resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.5.2.tgz",
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
"license": "MIT",
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
}
},
"node_modules/@codemirror/view": {
"version": "6.37.1",
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.37.1.tgz",
"integrity": "sha512-Qy4CAUwngy/VQkEz0XzMKVRcckQuqLYWKqVpDDDghBe5FSXSqfVrJn49nw3ePZHxRUz4nRmb05Lgi+9csWo4eg==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.5.0",
"crelt": "^1.0.6",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@colors/colors": { "node_modules/@colors/colors": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmmirror.com/@colors/colors/-/colors-1.6.0.tgz", "resolved": "https://registry.npmmirror.com/@colors/colors/-/colors-1.6.0.tgz",
@ -1174,6 +1269,41 @@
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@lezer/common": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.2.3.tgz",
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
"license": "MIT"
},
"node_modules/@lezer/highlight": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/@lezer/highlight/-/highlight-1.2.1.tgz",
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/json": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/@lezer/json/-/json-1.0.3.tgz",
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/lr": {
"version": "1.4.2",
"resolved": "https://registry.npmmirror.com/@lezer/lr/-/lr-1.4.2.tgz",
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@mapbox/geojson-area": { "node_modules/@mapbox/geojson-area": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmmirror.com/@mapbox/geojson-area/-/geojson-area-0.2.2.tgz", "resolved": "https://registry.npmmirror.com/@mapbox/geojson-area/-/geojson-area-0.2.2.tgz",
@ -1339,6 +1469,12 @@
"integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/@marijn/find-cluster-break": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"license": "MIT"
},
"node_modules/@napi-rs/wasm-runtime": { "node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.9", "version": "0.2.9",
"resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz", "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz",
@ -7343,6 +7479,21 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.1.tgz",
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"node_modules/color": { "node_modules/color": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz", "resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz",
@ -7597,6 +7748,12 @@
"node": ">= 14" "node": ">= 14"
} }
}, },
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
"node_modules/cron-parser": { "node_modules/cron-parser": {
"version": "4.9.0", "version": "4.9.0",
"resolved": "https://registry.npmmirror.com/cron-parser/-/cron-parser-4.9.0.tgz", "resolved": "https://registry.npmmirror.com/cron-parser/-/cron-parser-4.9.0.tgz",
@ -13715,6 +13872,12 @@
"integrity": "sha512-FL8EeKFFyNQv5cMnXI31CIMCsFarSVI2bF0U0ImeNE3g/F1IvJQyqzOXxPBRXiwQfyBTlbNe88jh1jFW0O/jiQ==", "integrity": "sha512-FL8EeKFFyNQv5cMnXI31CIMCsFarSVI2bF0U0ImeNE3g/F1IvJQyqzOXxPBRXiwQfyBTlbNe88jh1jFW0O/jiQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/style-mod": {
"version": "4.1.2",
"resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.2.tgz",
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
"license": "MIT"
},
"node_modules/stylehacks": { "node_modules/stylehacks": {
"version": "7.0.4", "version": "7.0.4",
"resolved": "https://registry.npmmirror.com/stylehacks/-/stylehacks-7.0.4.tgz", "resolved": "https://registry.npmmirror.com/stylehacks/-/stylehacks-7.0.4.tgz",
@ -14957,6 +15120,12 @@
"vue": "^3.2.0" "vue": "^3.2.0"
} }
}, },
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
"node_modules/web-streams-polyfill": { "node_modules/web-streams-polyfill": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "resolved": "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",

View File

@ -10,9 +10,13 @@
"postinstall": "nuxt prepare" "postinstall": "nuxt prepare"
}, },
"dependencies": { "dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.11.1",
"@codemirror/state": "^6.5.2",
"@mapbox/mapbox-gl-draw": "^1.5.0", "@mapbox/mapbox-gl-draw": "^1.5.0",
"@turf/turf": "^7.2.0", "@turf/turf": "^7.2.0",
"@vueuse/nuxt": "^12.7.0", "@vueuse/nuxt": "^12.7.0",
"codemirror": "^6.0.1",
"maplibre-gl": "^5.1.1", "maplibre-gl": "^5.1.1",
"nuxt": "^3.16.2", "nuxt": "^3.16.2",
"vue": "latest", "vue": "latest",

View File

@ -1,13 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
layout: "default", layout: "default",
}) });
</script> </script>
<template> <template>
<div class="w-full h-full relative"> <Map></Map>
<Map />
</div>
</template> </template>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -24,11 +24,12 @@
"sources": { "sources": {
"openmaptiles": { "openmaptiles": {
"type": "vector", "type": "vector",
"url": "/api/v1/pmtiles/zhejiang-pm" "url": "/api/v1/pmtiles/zhejiang-pm",
"maxzoom": 14
} }
}, },
"sprite": "https://openmaptiles.github.io/osm-bright-gl-style/sprite", "sprite": "http://localhost:8080/style/sprite/icons/icons",
"glyphs": "https://api.maptiler.com/fonts/{fontstack}/{range}.pbf?key={key}", "glyphs": "http://localhost:8080/style/font/{fontstack}/{range}.pbf",
"layers": [ "layers": [
{ {
"id": "background", "id": "background",