Skip to content

Commit

Permalink
Вывести слой ОКН в виде фоток в кружках (#136)
Browse files Browse the repository at this point in the history
  • Loading branch information
vatergo authored Jan 29, 2024
1 parent 9180e9f commit ec48a3e
Show file tree
Hide file tree
Showing 1,430 changed files with 2,874 additions and 1,480 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"plugin:jsx-a11y/recommended",
"plugin:react-hooks/recommended"
],
"ignorePatterns": ["next.config.js", "scripts/**/*.*js"],
"ignorePatterns": ["next.config.js", "scripts/**/*.*js", "build-scripts/**/*.*js"],
"parserOptions": {
"project": "./tsconfig.json"
},
Expand Down
9 changes: 9 additions & 0 deletions build-scripts/prepare-static/constants.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const VERCEL_PUBLIC_PATH = '/okn-static/';

export const VERCEL_OUTPUT_PATH = `./public${VERCEL_PUBLIC_PATH}`;

export const VERCEL_PUBLIC_IMAGES_PATH = `${VERCEL_PUBLIC_PATH}images/`;

export const IMAGES_URLS_PATH = `${VERCEL_OUTPUT_PATH}images/`;

export const PLACEMARKS_CACHE_PATH = `${VERCEL_OUTPUT_PATH}placemarks.json`;
149 changes: 149 additions & 0 deletions build-scripts/prepare-static/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import https from 'node:https';
import fs from 'node:fs';
import items from '../../public/ekb-okn.json' assert { type: "json" };
import { resize, optimize } from './prepareImages.mjs';
import { IMAGES_URLS_PATH, PLACEMARKS_CACHE_PATH, VERCEL_PUBLIC_IMAGES_PATH } from './constants.mjs';

const start = Date.now()

async function init() {
console.log('Prepare images')

await clearCachedImages(IMAGES_URLS_PATH);

const imageUrls = getImagesUrls(items.features);

console.log(`Download ${imageUrls.length} images`);
const images = await downloadImages(imageUrls)
.then(log('Resize images'))
.then(resize)
.then(log('Remove original size images'))
.then(removeOriginalImages)
.then(log('Optimize images'))
.then(optimize)
.catch(console.log);

console.log('Save metadata')
await saveMetadata(PLACEMARKS_CACHE_PATH, items.features, images)

console.log(`Finish in ${(Date.now() - start) / 1000} seconds`)
}
init()

async function clearCachedImages(path) {
if (fs.existsSync(path)) {
fs.rmSync(path, { recursive: true });
}

fs.mkdirSync(path, { recursive: true });
}

function getImagesUrls(items) {
return items.reduce((all, item) => {
const img = item.properties.img

if (Boolean(img)) {
if (typeof img === 'string') {
return all.concat(img)
}
return all.concat(img.url)
}
return all;
}, []);
}

async function downloadImages(urls) {
const downloadImage = (url) =>
new Promise((resolve, reject) => {
const ext = 'jpg';
const guid = getGUID(url);
const filename = `${guid}.${ext}`;
const file = fs.createWriteStream(IMAGES_URLS_PATH + filename);

https.get(url, (response) => {
response.pipe(file);
});

file.on('finish', () => {
file.close();
resolve({ id: guid, path: filename });
});

file.on('error', (e) => {
console.error(`Not loaded: ${filename}`, e);
reject();
});
});

return Promise.all(urls.map(downloadImage));
}

function getGUID(fileUrl) {
return new URL(fileUrl).pathname.split('/').at(-1).replace(/\D/g, '');
}

function log(message = '', getMessage = () => '') {
return (...args) => {
console.log(message, getMessage(...args));
return Promise.resolve(...args);
};
}

async function removeOriginalImages(items) {
await Promise.all(
items.map(
(item) =>
new Promise((resolve) => {
try {
fs.unlinkSync(IMAGES_URLS_PATH + item.path);
} catch (e) {
console.log(`Error remove ${e}`);
}
resolve();
}),
),
);
return items;
}

async function saveMetadata(cachPath, items, images) {
const imagesById = images.reduce((all, item) => {
all[item.id] = item;
return all;
}, {});

const updatedItems = items.map((item) => {
const img = item.properties.img

if (Boolean(img)) {
let guid = null;

if (typeof img === 'string') {
guid = getGUID(img);
}
guid = getGUID(img.url);
const image = imagesById[guid];

if(image) {
return {
...item,
preview: {
id: image.id,
m: {
...image.m,
src: `${VERCEL_PUBLIC_IMAGES_PATH}m_${image.path}`,
},
s: {
...image.s,
src: `${VERCEL_PUBLIC_IMAGES_PATH}s_${image.path}`,
},
}
}
}
}

return item;
});

fs.writeFileSync(cachPath, JSON.stringify(updatedItems));
}
62 changes: 62 additions & 0 deletions build-scripts/prepare-static/prepareImages.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import sharp from 'sharp';
import imagemin from 'imagemin';
import imageminMozjpeg from 'imagemin-mozjpeg';
import imageminPngquant from 'imagemin-pngquant';
import imageminSvgo from 'imagemin-svgo';
import { IMAGES_URLS_PATH } from './constants.mjs';

export async function optimize(items) {
await imagemin([`${IMAGES_URLS_PATH}/*.{jpg,jpeg,png,svg}`], {
destination: IMAGES_URLS_PATH,
plugins: [
imageminMozjpeg({ quality: 75 }),
imageminPngquant({ quality: [0.6, 0.8] }),
imageminSvgo(),
],
});

return items;
}

export async function resize(items) {
const resizeAndRoundOne = (filename, maxSize, prefix) =>
sharp(IMAGES_URLS_PATH + filename)
.composite([
{
input: Buffer.from(
`<svg>
<circle
cx="${maxSize / 2}"
cy="${maxSize / 2}"
r="${maxSize / 2}"
/>
</svg>`,
),
blend: 'dest-in',
},
])
.resize({
fit: sharp.fit.cover,
width: maxSize,
height: maxSize,
})
.toFile(IMAGES_URLS_PATH + prefix + '_' + filename)
.then(({ width, height }) => ({ width, height }));

const resizeOne = (filename, maxSize, prefix) =>
sharp(IMAGES_URLS_PATH + filename)
.resize({
fit: sharp.fit.inside,
width: maxSize,
})
.toFile(IMAGES_URLS_PATH + prefix + '_' + filename)
.then(({ width, height }) => ({ width, height }));

return Promise.all(
items.map(async ({ id, path }) => {
const m = await resizeOne(path, 800, 'm');
const s = await resizeAndRoundOne(path, 80, 's');
return { id, m, s, path };
}),
);
}
4 changes: 2 additions & 2 deletions features/OKN/CardContent/CardContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function OKNCardContent({ placemark }: { placemark: OknObject }) {

return placemark?.properties ? (
<div className={styles.popup}>
{placemark?.properties.img && (
{placemark?.properties.img && placemark?.preview && (
<a
href={placemark?.properties.img.url}
target="_blank"
Expand All @@ -35,7 +35,7 @@ export function OKNCardContent({ placemark }: { placemark: OknObject }) {
>
<img
key={placemark?.properties.img.title}
src={placemark?.properties.img.url}
src={placemark?.preview.m.src}
width={400}
height={256}
className={styles.popup__image}
Expand Down
28 changes: 28 additions & 0 deletions features/OKN/OknMarker.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.marker {
object-fit: cover;
border: 3px solid currentColor;
background: currentColor;
outline: 1px solid #000;
outline-offset: -4px;
border-radius: 100%;
min-width: 100%;
min-height: 100%;
cursor: pointer;
transition: all 0.1s;
position: relative;
transform-origin: center;
display: flex;
}

.marker:hover,
.marker.marker_open {
z-index: 9999 !important;
}

.marker:hover {
transform: scale(1.4);
}

.marker.marker_open {
transform: scale(1.7);
}
Loading

0 comments on commit ec48a3e

Please sign in to comment.