效果
代码
Java
maven
<!--exif元数据提取器-->
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.13.0</version>
</dependency>
<!--创建缩略图-->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.11</version>
</dependency>
<!--json-->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
<version>2.0.0-RC2</version>
</dependency>
<!--更改exif元数据-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-imaging</artifactId>
<version>1.0-alpha1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
WirteGps.java
public class WirteGps {
private String[] lonAndlans = {
"2.557053,27.918568",
"-3.386999,38.610584",
"1.344556,46.693275",
"4.585355,50.828274",
"-2.438676,54.490766",
"-7.883988,53.370002",
"19.028382,52.64909",
"27.103071,53.049358",
"30.17312,50.330044",
"24.861475,46.156955",
"22.137526,39.374672",
"19.465893,52.242634",
"8.320581,60.531888",
"-18.162903,65.050422",
"35.903303,39.618995",
"8.13747,46.866072",
"16.172202,45.603013",
"12.149518,43.106665"
};// 随机取的几个定位 https://tool.lu/coordinate/
public void changeExifMetadatas(final File jpegImageFile, final File dst)
throws IOException, ImageWriteException {
OutputStream os = null;
try {
TiffOutputSet outputSet = new TiffOutputSet();
{
final TiffOutputDirectory exifDirectory = outputSet
.getOrCreateExifDirectory();
exifDirectory
.removeField(ExifTagConstants.EXIF_TAG_APERTURE_VALUE);
exifDirectory.add(ExifTagConstants.EXIF_TAG_APERTURE_VALUE,
new RationalNumber(3, 10));
}
{
final double longitude = Double.parseDouble(RandomStr(lonAndlans).split(",")[0]);
final double latitude = Double.parseDouble(RandomStr(lonAndlans).split(",")[1]);
outputSet.setGPSInDegrees(longitude, latitude);
}
final TiffOutputDirectory exifDirectory = outputSet
.getOrCreateRootDirectory();
exifDirectory
.removeField(ExifTagConstants.EXIF_TAG_SOFTWARE);
exifDirectory.add(ExifTagConstants.EXIF_TAG_SOFTWARE,
"随便给个名字");
os = new FileOutputStream(dst);
os = new BufferedOutputStream(os);
new ExifRewriter().updateExifMetadataLossless(jpegImageFile, os,
outputSet);
} catch (ImageReadException e) {
System.out.println(jpegImageFile.getName());
e.printStackTrace();
} finally {
IOUtils.closeQuietly(os);
}
}
private static String RandomStr(String[] strs) {
int random_index = (int) (Math.random() * strs.length);
return strs[random_index];
}
public static void main(String[] args) throws IOException, ImageWriteException {
File file = new File("原来图片的文件夹");
File[] fs = file.listFiles();
for (File f : fs) {
if (!f.isDirectory()&&f.exists()) {
if (f.getName().endsWith(".jpeg")&&f.length()>0){
new WirteGps().changeExifMetadatas(f,new File("修改exif后的文件夹/"+f.getName()));
}
}
}
}
}
Extract.java
public class Extract {
public static void main(String[] args) throws Exception {
// 准备好图片
Path photoDir = Paths.get("原来的图片路径");
Path thumbnailDir = Paths.get("生成的略缩图路径");
Files.createDirectories(thumbnailDir);
Path photosJsonFile = Paths.get("需要生成的json文件路径");
try (Writer writer = Files.newBufferedWriter(photosJsonFile);
JsonGenerator jg = Json.createGenerator(writer)) {
jg.writeStartArray();
extractMetadata(jg, photoDir, thumbnailDir);
jg.writeEnd();
}
}
private static void extractMetadata(JsonGenerator jg, Path photoDir,
Path thumbnailDir) throws IOException {
Files.list(photoDir).forEach(photo -> {
try (InputStream is = Files.newInputStream(photo)) {
// 元数据
Metadata metadata = ImageMetadataReader.readMetadata(is);
// 经纬度
GpsDirectory gpsDirectory = metadata.getFirstDirectoryOfType(GpsDirectory.class);
// exif
ExifIFD0Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
Date date = directory.getDate(ExifIFD0Directory.TAG_DATETIME);
if (gpsDirectory != null) {
GeoLocation geoLocation = gpsDirectory.getGeoLocation();
if (geoLocation != null && !geoLocation.isZero()) {
if (!Files.exists(thumbnailDir.resolve(photo.getFileName()))) {
// 略缩图
Thumbnails.of(photo.toFile()).size(36, 36)
.toFiles(thumbnailDir.toFile(), Rename.NO_CHANGE);
}
jg.writeStartObject();
// 纬度
jg.write("lat", geoLocation.getLatitude());
// 经度
jg.write("lng", geoLocation.getLongitude());
// 图片名
jg.write("img", photo.getFileName().toString());
// 日期
if (date != null) {
jg.write("ts", (int) (date.getTime() / 1000));
}
jg.writeEnd();
jg.flush();
}
}
} catch (IOException | ImageProcessingException e) {
e.printStackTrace();
}
});
}
}
应用
mkdir app
cd app
npm init
修改package.json
{
"name": "geophotos",
"version": "1.0.0",
"main": "index.html",
"scripts": {
"prestart": "rimraf dist/*.*",
"start": "parcel --port 12345 src/index.html",
"prebuild": "rimraf dist/*.*",
"build": "parcel build src/index.html --no-source-maps --public-url ./",
"postbuild": "bread-compressor dist !*.jpg !*.jpeg",
"serve-dist": "ws --hostname localhost -d dist -p 12345 -o --log.format stats"
},
"devDependencies": {
"@babel/core": "7.9.6",
"@babel/plugin-transform-runtime": "7.9.6",
"bread-compressor-cli": "1.1.0",
"local-web-server": "4.0.0",
"parcel-bundler": "1.12.4",
"rimraf": "3.0.2"
},
"dependencies": {
"@google/markerclustererplus": "5.0.3",
"date-fns": "2.13.0",
"lg-autoplay.js": "1.0.0",
"lg-fullscreen.js": "1.1.0",
"lg-zoom.js": "1.0.1",
"lightgallery.js": "1.1.3"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}
在项目的根目录中创建.babelrc
{
"plugins": ["@babel/plugin-transform-runtime"]
}
- 创建
src
,dist
和dist/assets
文件夹 - 将
photos.json
文件复制到文件dist/assets
夹 - 将缩略图复制到
dist/assets/thumbnails
- 将照片复制到
dist/assets/photos
- 在
node_modules/@google/markerclustererplus/images
下找到
m1.png~m5.png
图片复制到到dist/assets
下重命名为1.png~5.png
npm install
mkdir src
mkdir dist
mkdir dist/assets
cd src
touch index.html
touch main.css
touch main.js
main.css
@import '../node_modules/lightgallery.js/dist/css/lightgallery.css';
#map {
position: absolute;
top: 10px;
bottom: 10px;
left: 10px;
right: 10px;
}
/* Outside white border */
.asset-map-image-marker {
background-color: gold;
border-radius: 5px;
cursor: pointer !important;
height: 40px;
margin-left: -20px; /* margin-left = -width/2 */
margin-top: -50px; /* margin-top = -height + arrow */
padding: 0px;
position: absolute;
width: 40px;
}
/* Arrow on bottom of container */
.asset-map-image-marker:after {
border-color: #ffffff transparent;
border-style: solid;
border-width: 10px 10px 0;
bottom: -10px;
content: '';
display: block;
left: 10px;
position: absolute;
width: 0;
}
/* Inner image container */
.asset-map-image-marker div.image {
background-position: center center;
background-size: cover;
border-radius: 5px;
height: 36px;
margin: 2px;
width: 36px;
}
HTMLMapMarker.js 缩略图显示为标记图标
/*
* https://blackatlascreative.com/blog/custom-clickable-google-map-markers-with-images/
* https://levelup.gitconnected.com/how-to-create-custom-html-markers-on-google-maps-9ff21be90e4b
* HTMLMapMarker Javascript class
* Extends the Google Maps OverlayView class
* Set up to accept our latlng, html for the div, and the map to attach it to as arguments
*/
export class HTMLMapMarker extends google.maps.OverlayView {
// Constructor accepting args
constructor(args) {
super();
this.latlng = args.latlng;
this.html = args.html;
this.photo = args.photo;
this.setMap(args.map);
}
// Create the div with content and add a listener for click events
createDiv() {
this.div = document.createElement('div');
this.div.style.position = 'absolute';
if (this.html) {
this.div.innerHTML = this.html;
}
google.maps.event.addDomListener(this.div, 'click', event => {
google.maps.event.trigger(this, 'click');
});
}
// Append to the overlay layer
// Appending to both overlayLayer and overlayMouseTarget which should allow this to be clickable
appendDivToOverlay() {
const panes = this.getPanes();
panes.overlayLayer.appendChild(this.div);
panes.overlayMouseTarget.appendChild(this.div);
}
// Position the div according to the coordinates
positionDiv() {
const point = this.getProjection().fromLatLngToDivPixel(this.latlng);
if (point) {
this.div.style.left = `${point.x}px`;
this.div.style.top = `${point.y}px`;
}
}
// Create the div and append to map
draw() {
if (!this.div) {
this.createDiv();
this.appendDivToOverlay();
}
this.positionDiv();
}
// Remove this from map
remove() {
if (this.div) {
this.div.parentNode.removeChild(this.div);
this.div = null;
}
}
// Return lat and long object
getPosition() {
return this.latlng;
}
// Return whether this is draggable
getDraggable() {
return false;
}
}
main.js
import { HTMLMapMarker } from './HTMLMapMarker.js';
import MarkerClusterer from '@google/markerclustererplus';
import format from 'date-fns/format'
import 'lightgallery.js';
import 'lg-fullscreen.js';
import 'lg-autoplay.js';
import 'lg-zoom.js';
let map;
const markers = [];
loadMap();
loadPhotos();
function loadMap() {
const latLng = new google.maps.LatLng(30.942088,121.378448);
const mapOptions = {
center: latLng,
zoom: 10,
mapTypeId: google.maps.MapTypeId.HYBRID
}
map = new google.maps.Map(document.getElementById('map'), mapOptions);
}
async function loadPhotos() {
const response = await fetch('assets/photos.json');
const photos = await response.json();
for (const photo of photos) {
drawMarker(photo);
}
new MarkerClusterer(map, markers, { imagePath: 'assets/', minimumClusterSize: 20 });
}
function drawMarker(photo) {
const marker = new HTMLMapMarker({
photo: photo.img,
latlng: new google.maps.LatLng(photo.lat, photo.lng),
html: `<div class="asset-map-image-marker"><div title="${photo.ts ? format(new Date(photo.ts * 1000), 'yyyy-MM-dd HH:mm') : ''}" class="image" style="background-image: url(assets/thumbnails/${photo.img})"></div></div>`
});
marker.addListener('click', () => {
const el = document.getElementById('lightgallery');
const lg = window.lgData[el.getAttribute('lg-uid')];
if (lg) {
lg.destroy(true);
}
lightGallery(el, {
dynamic: true,
dynamicEl: visiblePhotos(photo)
});
});
markers.push(marker);
}
function visiblePhotos(photo) {
const bounds = map.getBounds();
const result = [{ src: `assets/photos/${photo.img}` }];
for (const marker of markers) {
if (bounds.contains(marker.getPosition())) {
if (photo.img !== marker.photo) {
result.push({ src: `assets/photos/${marker.photo}` });
}
}
}
return result;
}
获取api
https://developers.google.com/maps/documentation/javascript/get-api-key
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GeoPhoto</title>
<link rel="stylesheet" type="text/css" href="main.css">
<link rel='shortcut icon' type='image/x-icon' href='favicon.ico' />
</head>
<body>
<div id="map"></div>
<div id="lightgallery"></div>
<script src="https://maps.googleapis.com/maps/api/js?key=你的api"></script>
<script src="main.js"></script>
</body>
</html>
高德地图版本
main.js
import format from 'date-fns/format'
import 'lightgallery.js';
import 'lg-fullscreen.js';
import 'lg-autoplay.js';
import 'lg-zoom.js';
let map; // 地图
const markers = []; // 标记
loadMap();
loadPhotos();
// 初始化
function loadMap() {
map = new AMap.Map('map', {
center:[113.87782499999999,22.792013888888892],
zoom:10
});
}
// 数据初始化
async function loadPhotos() {
const response = await fetch('assets/photos.json');
const photos = await response.json();
for (const photo of photos) {
drawMarker(photo);
}
map.add(markers);
}
// 标记与事件
function drawMarker(photo) {
var marker = new AMap.Marker({
content: `<div class="asset-map-image-marker"><div title="${photo.ts ? format(new Date(photo.ts * 1000), 'yyyy-MM-dd HH:mm') : ''}" class="image" style="background-image: url(assets/thumbnails/${photo.img})"></div></div>`,
position: new AMap.LngLat(photo.lng,photo.lat),
offset: new AMap.Pixel(-10, -10)
});
marker.photo=photo.img;
marker.on('click', () => {
const el = document.getElementById('lightgallery');
const lg = window.lgData[el.getAttribute('lg-uid')];
if (lg) {
lg.destroy(true);
}
lightGallery(el, {
dynamic: true,
dynamicEl: visiblePhotos(photo)
});
});
markers.push(marker);
}
// 当前视图范围的所有图片
function visiblePhotos(photo) {
let bounds = map.getBounds();
let result = [{ src: `assets/photos/${photo.img}` }];
for (const marker of markers) {
let position= marker.getPosition();
if (bounds.contains(position)) {
if (photo.img !== marker.photo) {
result.push({ src: `assets/photos/${marker.photo}` });
}
}
}
return result;
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GeoPhoto</title>
<link rel="stylesheet" type="text/css" href="main.css">
<link rel='shortcut icon' type='image/x-icon' href='favicon.ico' />
</head>
<body>
<div id="map"></div>
<div id="lightgallery"></div>
<script src="https://webapi.amap.com/maps?v=1.4.15&key=你的key"></script>
<script src="main.js"></script>
</body>
</html>
运行测试
npm run start
打包部署
npm run build
直接将dist
文件夹整个复制到服务器即可
例如nginx配置
location /photos{
alias /l0vo.com/dist;
index index.html;
}
Q.E.D.
Comments | 3 条评论