vector-styled
This commit is contained in:
parent
e48fae5509
commit
3f2be96395
5 changed files with 291 additions and 35 deletions
|
|
@ -6,6 +6,7 @@ export function finishMapOutput({
|
|||
seed,
|
||||
options,
|
||||
terrainTemplate,
|
||||
seaLevel,
|
||||
cityPopulationCap,
|
||||
stationInfluence,
|
||||
roadInfluence,
|
||||
|
|
@ -207,6 +208,7 @@ export function finishMapOutput({
|
|||
height: MAP_H,
|
||||
cellSize: CELL_SIZE,
|
||||
terrainTemplate,
|
||||
seaLevel,
|
||||
prefectureMask,
|
||||
prefectureBorder,
|
||||
prefectureRegionId,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export function generateMap(seedInput = 114514, options = {}) {
|
|||
const terrain = generateTerrainAndRivers(seed);
|
||||
const {
|
||||
terrainTemplate,
|
||||
seaLevel,
|
||||
elevation,
|
||||
moisture,
|
||||
slope,
|
||||
|
|
@ -63,7 +64,7 @@ export function generateMap(seedInput = 114514, options = {}) {
|
|||
});
|
||||
|
||||
return finishMapOutput({
|
||||
seed, options, terrainTemplate, cityPopulationCap, stationInfluence, roadInfluence, railInfluence2,
|
||||
seed, options, terrainTemplate, seaLevel, cityPopulationCap, stationInfluence, roadInfluence, railInfluence2,
|
||||
elevation, moisture, slope, sea, ocean, lake, river, floodplain, plain, agriculture, settlementCluster, ridgeField, valleyField, basinField, coastalLowland, flowAccum, erosionField, depositionField,
|
||||
arcSpineField, branchRidgeField, depositionalLowland, alluvialFanField, deltaField, naturalBarrierScore,
|
||||
villages, ports, crossings, passes, markets, castles, castleTowns, premodernRoads, minorRoads, modernCities, populationDensity,
|
||||
|
|
|
|||
|
|
@ -1288,6 +1288,7 @@ export function generateTerrainAndRivers(seed) {
|
|||
|
||||
return {
|
||||
terrainTemplate,
|
||||
seaLevel,
|
||||
elevation,
|
||||
moisture,
|
||||
slope,
|
||||
|
|
|
|||
2
names.js
2
names.js
|
|
@ -34,7 +34,7 @@ export const NAME_KANJI_POOLS = {
|
|||
],
|
||||
|
||||
coastalTerrain: [
|
||||
"津", "浦", "津", "崎",
|
||||
"津", "浦", "ヶ浦", "津", "崎",
|
||||
"島", "磯", "潟", "湊", "津",
|
||||
"州", "洲", "瀬", "砂", "潮", "塩", "汐",
|
||||
"泊", "江", "浦", "灘", "入",
|
||||
|
|
|
|||
318
renderer.js
318
renderer.js
|
|
@ -1,5 +1,259 @@
|
|||
import { CELL_SIZE, MAP_H, MAP_W, clamp, indexOf } from "./mapUtils.js";
|
||||
|
||||
|
||||
const segmentVectorCache = new WeakMap();
|
||||
const pathVectorCache = new WeakMap();
|
||||
const coastlineCache = new WeakMap();
|
||||
|
||||
function pointKey(p) {
|
||||
return `${p[0]},${p[1]}`;
|
||||
}
|
||||
|
||||
function parsePointKey(key) {
|
||||
return key.split(",").map(Number);
|
||||
}
|
||||
|
||||
function samePoint(a, b, eps = 1e-6) {
|
||||
return Math.abs(a[0] - b[0]) <= eps && Math.abs(a[1] - b[1]) <= eps;
|
||||
}
|
||||
|
||||
function perpendicularDistance(p, a, b) {
|
||||
const dx = b[0] - a[0];
|
||||
const dy = b[1] - a[1];
|
||||
const len2 = dx * dx + dy * dy;
|
||||
if (len2 <= 1e-9) return Math.hypot(p[0] - a[0], p[1] - a[1]);
|
||||
const t = clamp(((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2, 0, 1);
|
||||
const x = a[0] + dx * t;
|
||||
const y = a[1] + dy * t;
|
||||
return Math.hypot(p[0] - x, p[1] - y);
|
||||
}
|
||||
|
||||
function simplifyRdp(points, tolerance = 0.05) {
|
||||
if (!points || points.length <= 2) return points || [];
|
||||
let bestIndex = -1;
|
||||
let bestDistance = -1;
|
||||
const first = points[0];
|
||||
const last = points[points.length - 1];
|
||||
for (let i = 1; i < points.length - 1; i++) {
|
||||
const d = perpendicularDistance(points[i], first, last);
|
||||
if (d > bestDistance) {
|
||||
bestDistance = d;
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
if (bestDistance <= tolerance) return [first, last];
|
||||
const left = simplifyRdp(points.slice(0, bestIndex + 1), tolerance);
|
||||
const right = simplifyRdp(points.slice(bestIndex), tolerance);
|
||||
return left.slice(0, -1).concat(right);
|
||||
}
|
||||
|
||||
function removeCollinear(points) {
|
||||
if (!points || points.length <= 2) return points || [];
|
||||
const closed = samePoint(points[0], points[points.length - 1]);
|
||||
const core = closed ? points.slice(0, -1) : points.slice();
|
||||
if (core.length <= 2) return points;
|
||||
const out = [];
|
||||
const count = core.length;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const prev = core[(i - 1 + count) % count];
|
||||
const cur = core[i];
|
||||
const next = core[(i + 1) % count];
|
||||
if (!closed && (i === 0 || i === count - 1)) {
|
||||
out.push(cur);
|
||||
continue;
|
||||
}
|
||||
const ax = cur[0] - prev[0];
|
||||
const ay = cur[1] - prev[1];
|
||||
const bx = next[0] - cur[0];
|
||||
const by = next[1] - cur[1];
|
||||
if (Math.abs(ax * by - ay * bx) > 1e-9) out.push(cur);
|
||||
}
|
||||
if (closed && out.length) out.push(out[0]);
|
||||
return out;
|
||||
}
|
||||
|
||||
function chaikin(points, iterations = 1, closed = false) {
|
||||
if (!points || points.length < 3 || iterations <= 0) return points || [];
|
||||
let result = points.slice();
|
||||
for (let iter = 0; iter < iterations; iter++) {
|
||||
const src = closed && samePoint(result[0], result[result.length - 1]) ? result.slice(0, -1) : result;
|
||||
if (src.length < 3) break;
|
||||
const next = [];
|
||||
if (!closed) next.push(src[0]);
|
||||
const limit = closed ? src.length : src.length - 1;
|
||||
for (let i = 0; i < limit; i++) {
|
||||
const a = src[i];
|
||||
const b = src[(i + 1) % src.length];
|
||||
next.push([a[0] * 0.75 + b[0] * 0.25, a[1] * 0.75 + b[1] * 0.25]);
|
||||
next.push([a[0] * 0.25 + b[0] * 0.75, a[1] * 0.25 + b[1] * 0.75]);
|
||||
}
|
||||
if (!closed) next.push(src[src.length - 1]);
|
||||
if (closed && next.length) next.push(next[0]);
|
||||
result = next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function traceSegmentChain(edges, adjacency, used, startKey, firstEdgeIndex) {
|
||||
const line = [parsePointKey(startKey)];
|
||||
let currentKey = startKey;
|
||||
let nextEdgeIndex = firstEdgeIndex;
|
||||
|
||||
while (nextEdgeIndex !== undefined && nextEdgeIndex !== null && !used[nextEdgeIndex]) {
|
||||
used[nextEdgeIndex] = 1;
|
||||
const edge = edges[nextEdgeIndex];
|
||||
const toKey = edge.aKey === currentKey ? edge.bKey : edge.aKey;
|
||||
line.push(parsePointKey(toKey));
|
||||
currentKey = toKey;
|
||||
|
||||
if (currentKey === startKey) break;
|
||||
const degree = adjacency.get(currentKey)?.length || 0;
|
||||
if (degree !== 2) break;
|
||||
nextEdgeIndex = (adjacency.get(currentKey) || []).find((idx) => !used[idx]);
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
function segmentChains(segments) {
|
||||
if (!segments?.length) return [];
|
||||
const edges = [];
|
||||
const adjacency = new Map();
|
||||
|
||||
for (const seg of segments) {
|
||||
const aKey = pointKey(seg[0]);
|
||||
const bKey = pointKey(seg[1]);
|
||||
if (aKey === bKey) continue;
|
||||
const edgeIndex = edges.length;
|
||||
edges.push({ aKey, bKey });
|
||||
if (!adjacency.has(aKey)) adjacency.set(aKey, []);
|
||||
if (!adjacency.has(bKey)) adjacency.set(bKey, []);
|
||||
adjacency.get(aKey).push(edgeIndex);
|
||||
adjacency.get(bKey).push(edgeIndex);
|
||||
}
|
||||
|
||||
const used = new Uint8Array(edges.length);
|
||||
const chains = [];
|
||||
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
if (used[i]) continue;
|
||||
const edge = edges[i];
|
||||
const aDegree = adjacency.get(edge.aKey)?.length || 0;
|
||||
const bDegree = adjacency.get(edge.bKey)?.length || 0;
|
||||
if (aDegree === 2 && bDegree === 2) continue;
|
||||
const startKey = aDegree !== 2 ? edge.aKey : edge.bKey;
|
||||
chains.push(traceSegmentChain(edges, adjacency, used, startKey, i));
|
||||
}
|
||||
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
if (used[i]) continue;
|
||||
chains.push(traceSegmentChain(edges, adjacency, used, edges[i].aKey, i));
|
||||
}
|
||||
|
||||
return chains.filter((line) => line.length >= 2);
|
||||
}
|
||||
|
||||
function vectorizeSegments(segments, { iterations = 2, tolerance = 0.08 } = {}) {
|
||||
if (!segments?.length) return [];
|
||||
const cacheKey = `${iterations}:${tolerance}`;
|
||||
let cachedByOption = segmentVectorCache.get(segments);
|
||||
if (!cachedByOption) {
|
||||
cachedByOption = new Map();
|
||||
segmentVectorCache.set(segments, cachedByOption);
|
||||
}
|
||||
if (cachedByOption.has(cacheKey)) return cachedByOption.get(cacheKey);
|
||||
|
||||
const polylines = segmentChains(segments).map((line) => {
|
||||
const cleaned = removeCollinear(line);
|
||||
const closed = cleaned.length > 2 && samePoint(cleaned[0], cleaned[cleaned.length - 1]);
|
||||
const smoothed = chaikin(cleaned, iterations, closed);
|
||||
if (closed) return smoothed;
|
||||
return simplifyRdp(smoothed, tolerance);
|
||||
}).filter((line) => line.length >= 2);
|
||||
|
||||
cachedByOption.set(cacheKey, polylines);
|
||||
return polylines;
|
||||
}
|
||||
|
||||
function getCoastlineSegments(map) {
|
||||
if (!map?.sea) return [];
|
||||
const cached = coastlineCache.get(map.sea);
|
||||
if (cached) return cached;
|
||||
|
||||
const segments = [];
|
||||
for (let y = 0; y < MAP_H; y++) {
|
||||
for (let x = 0; x < MAP_W; x++) {
|
||||
const i = indexOf(x, y);
|
||||
const a = Boolean(map.sea[i]);
|
||||
if (x + 1 < MAP_W) {
|
||||
const b = Boolean(map.sea[indexOf(x + 1, y)]);
|
||||
if (a !== b) segments.push([[x + 1, y], [x + 1, y + 1]]);
|
||||
}
|
||||
if (y + 1 < MAP_H) {
|
||||
const b = Boolean(map.sea[indexOf(x, y + 1)]);
|
||||
if (a !== b) segments.push([[x, y + 1], [x + 1, y + 1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
coastlineCache.set(map.sea, segments);
|
||||
return segments;
|
||||
}
|
||||
|
||||
function vectorPath(path) {
|
||||
if (!path || path.length < 2) return [];
|
||||
const cached = pathVectorCache.get(path);
|
||||
if (cached) return cached;
|
||||
|
||||
const points = path.map(([x, y]) => [x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2]);
|
||||
const simplified = simplifyRdp(points, CELL_SIZE * 0.34);
|
||||
const smoothed = chaikin(simplified, path.length > 6 ? 1 : 0, false);
|
||||
pathVectorCache.set(path, smoothed);
|
||||
return smoothed;
|
||||
}
|
||||
|
||||
function drawPolylinePoints(ctx, points) {
|
||||
if (!points || points.length < 2) return;
|
||||
ctx.moveTo(points[0][0], points[0][1]);
|
||||
for (let k = 1; k < points.length; k++) ctx.lineTo(points[k][0], points[k][1]);
|
||||
}
|
||||
|
||||
function drawVectorSegments(ctx, segments, color, width, dashed = false, vectorOptions = {}) {
|
||||
const { offsetX = 0, offsetY = 0, ...shapeOptions } = vectorOptions || {};
|
||||
const polylines = vectorizeSegments(segments, shapeOptions);
|
||||
if (!polylines.length) return;
|
||||
ctx.save();
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = width;
|
||||
ctx.lineCap = "round";
|
||||
ctx.lineJoin = "round";
|
||||
if (dashed) ctx.setLineDash([6, 5]);
|
||||
|
||||
for (const line of polylines) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo((line[0][0] + offsetX) * CELL_SIZE, (line[0][1] + offsetY) * CELL_SIZE);
|
||||
for (let k = 1; k < line.length; k++) ctx.lineTo((line[k][0] + offsetX) * CELL_SIZE, (line[k][1] + offsetY) * CELL_SIZE);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function sampleCellIndex(fx, fy) {
|
||||
const x = Math.max(0, Math.min(MAP_W - 1, Math.floor(fx)));
|
||||
const y = Math.max(0, Math.min(MAP_H - 1, Math.floor(fy)));
|
||||
return indexOf(x, y);
|
||||
}
|
||||
|
||||
function isWaterSample(map, fx, fy) {
|
||||
// The generated terrain arrays are cell-centered, while pixels are drawn across
|
||||
// each cell. Water/land classification must therefore follow the discrete sea
|
||||
// mask, not the interpolated elevation value. Interpolating elevation near a
|
||||
// coast makes the right/bottom side of land cells inherit sea values and leaves
|
||||
// visible unpainted strips inside the smoothed coastline.
|
||||
return Boolean(map.sea[sampleCellIndex(fx, fy)]);
|
||||
}
|
||||
|
||||
|
||||
function distToNearest(points, x, y, fallback = 999) {
|
||||
let best = fallback;
|
||||
for (const p of points) best = Math.min(best, Math.hypot(p.x - x, p.y - y));
|
||||
|
|
@ -16,12 +270,14 @@ function blendOutside(color, isInside) {
|
|||
}
|
||||
|
||||
function fieldSample(field, fx, fy) {
|
||||
const x0 = Math.max(0, Math.min(MAP_W - 1, Math.floor(fx)));
|
||||
const y0 = Math.max(0, Math.min(MAP_H - 1, Math.floor(fy)));
|
||||
const sx = Math.max(0, Math.min(MAP_W - 1, fx));
|
||||
const sy = Math.max(0, Math.min(MAP_H - 1, fy));
|
||||
const x0 = Math.floor(sx);
|
||||
const y0 = Math.floor(sy);
|
||||
const x1 = Math.max(0, Math.min(MAP_W - 1, x0 + 1));
|
||||
const y1 = Math.max(0, Math.min(MAP_H - 1, y0 + 1));
|
||||
const tx = fx - x0;
|
||||
const ty = fy - y0;
|
||||
const tx = sx - x0;
|
||||
const ty = sy - y0;
|
||||
|
||||
const a = field[indexOf(x0, y0)];
|
||||
const b = field[indexOf(x1, y0)];
|
||||
|
|
@ -32,13 +288,13 @@ function fieldSample(field, fx, fy) {
|
|||
}
|
||||
|
||||
function terrainColorContinuous(map, fx, fy, mode) {
|
||||
const i = indexOf(Math.max(0, Math.min(MAP_W - 1, Math.floor(fx))), Math.max(0, Math.min(MAP_H - 1, Math.floor(fy))));
|
||||
const i = sampleCellIndex(fx, fy);
|
||||
const isInside = Boolean(map.prefectureMask[i]);
|
||||
|
||||
let color;
|
||||
|
||||
if (map.sea[i]) {
|
||||
const depth = clamp((0.35 - fieldSample(map.elevation, fx, fy)) * 2.4);
|
||||
if (isWaterSample(map, fx, fy)) {
|
||||
const depth = clamp(((map.seaLevel ?? 0.285) + 0.065 - fieldSample(map.elevation, fx, fy)) * 2.4);
|
||||
color = [Math.round(170 + depth * 5), Math.round(218 + depth * 10), Math.round(255 - depth * 5)];
|
||||
} else if (mode === "suitability") {
|
||||
const a = fieldSample(map.agriculture, fx, fy);
|
||||
|
|
@ -152,7 +408,8 @@ function drawBase(ctx, map, mode, continuousTerrain) {
|
|||
}
|
||||
|
||||
function drawPath(ctx, path, color, width, dashed = false) {
|
||||
if (!path || path.length < 2) return;
|
||||
const points = vectorPath(path);
|
||||
if (points.length < 2) return;
|
||||
ctx.save();
|
||||
ctx.lineCap = "round";
|
||||
ctx.lineJoin = "round";
|
||||
|
|
@ -160,17 +417,15 @@ function drawPath(ctx, path, color, width, dashed = false) {
|
|||
ctx.lineWidth = width;
|
||||
if (dashed) ctx.setLineDash([8, 6]);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(path[0][0] * CELL_SIZE + CELL_SIZE / 2, path[0][1] * CELL_SIZE + CELL_SIZE / 2);
|
||||
for (let k = 1; k < path.length; k++) {
|
||||
ctx.lineTo(path[k][0] * CELL_SIZE + CELL_SIZE / 2, path[k][1] * CELL_SIZE + CELL_SIZE / 2);
|
||||
}
|
||||
drawPolylinePoints(ctx, points);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// 魚の骨(私鉄記号)スタイルを描画するための専用関数
|
||||
function drawRailway(ctx, path, color, lineWidth, tickLen, spacing) {
|
||||
if (!path || path.length < 2) return;
|
||||
const points = vectorPath(path);
|
||||
if (points.length < 2) return;
|
||||
ctx.save();
|
||||
ctx.lineCap = "butt";
|
||||
ctx.lineJoin = "round";
|
||||
|
|
@ -179,33 +434,27 @@ function drawRailway(ctx, path, color, lineWidth, tickLen, spacing) {
|
|||
// 中心の実線を描画
|
||||
ctx.lineWidth = lineWidth;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(path[0][0] * CELL_SIZE + CELL_SIZE / 2, path[0][1] * CELL_SIZE + CELL_SIZE / 2);
|
||||
for (let k = 1; k < path.length; k++) {
|
||||
ctx.lineTo(path[k][0] * CELL_SIZE + CELL_SIZE / 2, path[k][1] * CELL_SIZE + CELL_SIZE / 2);
|
||||
}
|
||||
drawPolylinePoints(ctx, points);
|
||||
ctx.stroke();
|
||||
|
||||
// 棘(クロスハッチ)を描画
|
||||
ctx.lineWidth = 1.0;
|
||||
ctx.beginPath();
|
||||
let leftover = 0;
|
||||
for (let k = 0; k < path.length - 1; k++) {
|
||||
const x1 = path[k][0] * CELL_SIZE + CELL_SIZE / 2;
|
||||
const y1 = path[k][1] * CELL_SIZE + CELL_SIZE / 2;
|
||||
const x2 = path[k+1][0] * CELL_SIZE + CELL_SIZE / 2;
|
||||
const y2 = path[k+1][1] * CELL_SIZE + CELL_SIZE / 2;
|
||||
let carry = spacing * 0.5;
|
||||
for (let k = 0; k < points.length - 1; k++) {
|
||||
const [x1, y1] = points[k];
|
||||
const [x2, y2] = points[k + 1];
|
||||
const dx = x2 - x1;
|
||||
const dy = y2 - y1;
|
||||
const dist = Math.hypot(dx, dy);
|
||||
if (dist === 0) continue;
|
||||
|
||||
// 法線(直角)ベクトル
|
||||
if (dist <= 0.001) continue;
|
||||
|
||||
const nx = dx / dist;
|
||||
const ny = dy / dist;
|
||||
const px = -ny * (tickLen / 2);
|
||||
const py = nx * (tickLen / 2);
|
||||
|
||||
let d = (spacing / 2) + leftover;
|
||||
let d = carry;
|
||||
while (d < dist) {
|
||||
const cx = x1 + nx * d;
|
||||
const cy = y1 + ny * d;
|
||||
|
|
@ -213,7 +462,7 @@ function drawRailway(ctx, path, color, lineWidth, tickLen, spacing) {
|
|||
ctx.lineTo(cx - px, cy - py);
|
||||
d += spacing;
|
||||
}
|
||||
leftover = d - dist;
|
||||
carry = d - dist;
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
|
|
@ -357,6 +606,9 @@ export function drawMap(canvas, map, options) {
|
|||
// 1. Base Terrain & Urban
|
||||
drawBase(ctx, map, mode, true);
|
||||
drawUrbanAreas(ctx, map, mode);
|
||||
const coastSegments = getCoastlineSegments(map);
|
||||
drawVectorSegments(ctx, coastSegments, "rgba(120, 175, 210, 0.22)", 2.2, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
|
||||
drawVectorSegments(ctx, coastSegments, "rgba(248, 250, 242, 0.68)", 1.1, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
|
||||
|
||||
// 2. Rivers
|
||||
const waterBlue = "rgba(160, 205, 240, 1)";
|
||||
|
|
@ -371,18 +623,18 @@ export function drawMap(canvas, map, options) {
|
|||
|
||||
// 3. Borders
|
||||
if (showAdmin && map.adminBorders) {
|
||||
drawSegments(ctx, map.adminBorders, "rgba(255, 255, 255, 0.8)", 2.8, false);
|
||||
drawSegments(ctx, map.adminBorders, "rgba(150, 140, 150, 0.9)", 1.2, true);
|
||||
drawVectorSegments(ctx, map.adminBorders, "rgba(255, 255, 255, 0.8)", 2.8, false, { iterations: 1, tolerance: 0.05, offsetX: -0.5, offsetY: -0.5 });
|
||||
drawVectorSegments(ctx, map.adminBorders, "rgba(150, 140, 150, 0.9)", 1.2, true, { iterations: 1, tolerance: 0.05, offsetX: -0.5, offsetY: -0.5 });
|
||||
}
|
||||
if (mode === "admin-debug" || mode === "borders-debug") {
|
||||
drawDebugCells(ctx, map, map.naturalBarrierScore, (v) => `rgba(255, 120, 40, ${0.06 + v * 0.18})`);
|
||||
if (map.adminDebug?.compartmentBorders) drawSegments(ctx, map.adminDebug.compartmentBorders, "rgba(60, 110, 170, 0.42)", 0.8, true);
|
||||
for (const p of map.adminDebug?.lowlandAdminSeeds || []) dot(ctx, p, 3.2, "rgba(255,255,255,0.9)", "rgba(40,150,95,0.95)");
|
||||
if (map.regionalPrefectureBorders) drawSegments(ctx, map.regionalPrefectureBorders, "rgba(70, 55, 95, 0.95)", 2.4, false);
|
||||
if (map.regionalPrefectureBorders) drawVectorSegments(ctx, map.regionalPrefectureBorders, "rgba(70, 55, 95, 0.95)", 2.4, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
|
||||
}
|
||||
|
||||
drawSegments(ctx, map.prefectureBorder, "rgba(255, 255, 255, 0.95)", 5.0, false);
|
||||
drawSegments(ctx, map.prefectureBorder, "rgba(110, 90, 110, 1)", 2.2, true);
|
||||
drawVectorSegments(ctx, map.prefectureBorder, "rgba(255, 255, 255, 0.95)", 5.0, false, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
|
||||
drawVectorSegments(ctx, map.prefectureBorder, "rgba(110, 90, 110, 1)", 2.2, true, { iterations: 2, tolerance: 0.06, offsetX: -0.5, offsetY: -0.5 });
|
||||
|
||||
if (!showFeatures) return;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue