브라우저에서 지도 인쇄, 이야기

Stephen Monroe, Unsplash에서 촬영

머리말


지도는 인터넷상에서 상호작용식(예를 들어 구글 지도)이든 정적 이미지든 흔히 볼 수 있다.그러나 이런 지도를 현실 세계로 옮겨야 할 때도 있다.그래, 네가 알아맞혔어!이것이 바로 인쇄라는 것이다.
이 글을 더욱 매력적으로 만들기 위해서, 우리는 상상 속의 장면을 묘사할 것이다. 당신은 스위스를 횡단하는 도보 여행을 계획하고 있으며, 당신은 특정 지역의 노선도를 인쇄하고 싶다.분명히 종이 위에서 거리를 측정할 수 있는 것이 관건이다.

용기와 결심을 가지고 당신의 도보 지도를 인쇄하기 시작하세요!도전을 두려워하지 않기 때문에, 이 점을 돕기 위해 아주 간단한 웹 응용 프로그램을 구축할 것이다.

지도 인쇄에 관한 몇 가지 생각


종이 지도와 디지털 지도는 인터넷에서 지도를 복사하거나 붙이거나 캡처한 다음에 간단하게 워드 문서에 넣는 등 많은 정보를 공유한다. 이것은 매우 유혹적이다.이런 방법은 효과가 있지만 한계가 크고 한 가지 일을 둘러싸고 있다...

악명 높은 신문부


DPI는 인치당 포인트 수를 나타냅니다.점은 가장 작은 그리기 가능한 단위, 잉크(프린터용) 또는 픽셀(스크린용)을 나타낸다.DPI 값은 기본적으로 1인치 내에 몇 개의 작은 점을 그릴 수 있는지를 나타내는 비율입니다.
더 높은 것은 더 많은 세부 사항을 의미합니다. 일반적으로 DPI 값이 300이라고 가정하면 예상한 인쇄 품질이 가장 좋습니다.그러나 컴퓨터 화면의 DPI 값은 보통 300보다 훨씬 낮고 사전에 믿을 수 없다.
따라서 복사해서 붙인 이미지가 종이에 있으면 흐릿해 보일 수밖에 없다.이 밖에도 우리는 규모의 기미가 없을 것이다.

여기에 전문적인 소프트웨어 도움말이 있다


일부 전용 소프트웨어는 고화질 지도를 인쇄하는데, 예를 들어 Mapfish Print은 백엔드 API로 사용할 수 있다.데이터 소스, 지리적 위치, 배율, 용지 크기 및 DPI를 지정하면 Mapfish Print는 그림과 같이 전체 PDF 문서를 생성합니다.그것을 너에게 돌려주겠다.모든 것이 다 좋아요!
그러나 본고에서 우리는 inkmap을 더욱 상세하게 이해할 것이다. 이것은 브라우저 내부에서 지도를 인쇄할 수 있는 라이브러리이기 때문에 원격 API가 필요하지 않다.
이제 우리 정상 궤도로 돌아갑시다!

도보 여행 코스를 인쇄하는 프로그램 (도보 여행의 의미를 기억하고 있다면)


어떤 종류의 코드를 작성하기 전에 데이터 원본이 필요합니다. 이 예는hiketrails입니다.다행히도 스위스 연방지형국은 인터넷에서 이 데이터를 무료로 발표했다: Swisstopo tiled map services
우리는 우리가 필요로 하는 모든 것을 가지고 있다. 우리는 응용 프로그램을 만들 수 있다.

일반적 방법


우리는 너무 잊어버리지 말아야 할지도 모른다. 응용 프로그램은 상호작용 지도와 '인쇄' 단추만 포함할 것이다.인터랙티브 맵에서 인쇄할 영역을 나타내는 직사각형을 그릴 것입니다.마지막으로, 우리는 사용자에게 이 구역의 크기를 이동하고 조정할 가능성을 제공할 것이다.
"인쇄"단추를 눌렀을 때, 우리는 inkmap의 print 방법을 호출하여 최종적으로 인쇄 가능한 PDF 문서를 만들 것입니다.쉬웠어

초안


응용 프로그램 구축에 대해 자세히 소개하지 않겠습니다. 영감이 필요하시면 최종 프로젝트 here을 보십시오.
요컨대, 당신은 npm로 당신의 프로젝트를 초기화하고, Webpack과friends를 설치해야 합니다™ 응용 프로그램을 설정하려면 다음과 같이 하십시오.
$ npm init
$ npm install --save-dev webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env style-loader css-loader
내가 사용하는 웹 패키지 설정은 here이다.

Note that I would have preferred to use Parcel for bundling as it is much easier to setup, but unfortunately this will fail because of the dependencies used in the project


다음은 런타임 종속성으로 OpenLayers을 추가합니다.
$ npm install --save ol
그런 다음 프로젝트 디렉토리에 두 개의 파일을 생성합니다.

색인html


<!DOCTYPE html>
<html>
<head>
  <title>hiking trails map generator</title>
  <style>
      html {
          height: 100%;
      }
      body {
          height: 100%;
          margin: 0;
          background: #f6f6f6;
          display: flex;
          flex-direction: column;
          justify-content: center;
          align-items: center;
      }
      #map-root {
          width: 800px;
          height: 600px;
          margin-bottom: 20px;
          border-radius: 3px;
          border: 1px solid grey;
      }
  </style>
</head>
<body>
  <p>
    Use the map to select an area and click the button to print it.
  </p>
  <div id="map-root"></div>
  <button type="button" id="print-btn">Print</button>

  <!-- include the script at the end
       to make sure the page is loaded -->
  <script src="./app.js"></script>
</body>
</html>

응용 프로그램.js


import { fromLonLat } from 'ol/proj';
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';

// imports the OL stylesheet for nicer buttons!
import 'ol/ol.css';

// compute the map center from longitude and latitude
const mapCenter = fromLonLat([8.32, 46.90]);

// a simple OpenStreetMap layer (for development purposes)
const osmLayer = new TileLayer({
  source: new OSM()
});

// create the interactive map
const map = new Map({
  target: 'map-root',
  view: new View({
    zoom: 7,
    center: mapCenter,
    constrainResolution: true
  }),
  layers: [osmLayer]
});
지금 당신은 webpack serve --open을 실행할 수 있을 것입니다. 당신의 앱이 신기하게 당신의 브라우저에 나타나는 것을 볼 수 있습니다!

이 모든 것은 너무 빨리 발생했다.

봤어!상호작용


OpenLayers API을 사용하면 맵에 직사각형으로 DIN 용지 형식 (아시다시피 A-series) 과 일치하는 대상을 추가할 것입니다.
수정하기 편리하도록, 우리는 아주 좋은 확장 라이브러리 ol-ext을 사용할 것이다. 더욱 구체적으로 말하면 Transform interaction이다.설치하려면 다음과 같이 하십시오.
$ npm install --save ol-ext
마지막으로, "인쇄"단추에 이벤트 처리 프로그램을 연결해서 직사각형 좌표를 출력할 것입니다. (다음 단계를 준비할 것입니다.)

응용 프로그램.js


// add these at the top of the file
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import Feature from 'ol/Feature';
import { Polygon } from 'ol/geom';
import { always as conditionAlways } from 'ol/events/condition';
import TransformInteraction from 'ol-ext/interaction/Transform';

// ...

// our rectangle (width to height ratio is √2
// as per DIN paper formats)
const rectWidth = 100000;
const rectHeight = rectWidth / Math.sqrt(2);
const rectangle = new Feature({
  geometry: new Polygon([[
    [mapCenter[0] - rectWidth, mapCenter[1] + rectHeight],
    [mapCenter[0] + rectWidth, mapCenter[1] + rectHeight],
    [mapCenter[0] + rectWidth, mapCenter[1] - rectHeight],
    [mapCenter[0] - rectWidth, mapCenter[1] - rectHeight],
  ]])
});

// this vector layer will contain our rectangle
const vectorLayer = new VectorLayer({
  source: new VectorSource({
    features: [rectangle]
  })
});

// this will give the user the possibility to move the
// rectangle around and resize it by dragging its corners
const transform = new TransformInteraction({
  layers: vectorLayer,
  stretch: false,
  keepAspectRatio: conditionAlways,
  rotate: false
});

// create the interactive map
const map = new Map({
  // ...
  layers: [osmLayer, vectorLayer]
});

map.addInteraction(transform);

// bind the print button click handler
document.getElementById('print-btn')
  .addEventListener('click', () => {
    const rectangleCoords = JSON.stringify(
      rectangle.getGeometry().getCoordinates()
    );
    console.log(rectangleCoords);
  });
위대하다만약 모든 것이 정상이라면 직사각형을 이동할 수 있어야 한다. '인쇄' 를 눌렀을 때 수정된 좌표가 컨트롤러에 나타나는 것을 볼 수 있을 것이다.

나 10분 했어?
이 좌표들은 Web Mercator 투영으로 표시되며, 잠시 후 위도와 경도 값으로 전환해야 합니다.
다음은 까다로운 부분: 직사각형 안의 내용을 인쇄합니다.

온화한 수학이 곧 다가온다


프린터 파트너 inkmap을 설치할 때가 되었습니다.
$ npm install --save @camptocamp/inkmap
inkmap은 print 함수로 간단한 API를 제공합니다. JSON 규범이 필요합니다.JSON 사양은 다음과 같습니다.
{
  "layers": [
    // a list of data sources
  ],
  "size": [
    // expected map size
  ],
  "center": [
    // map center as longitude, latitude
  ],
  "dpi": // ever heard about this one?
  "scale": // this is the scale denominator
  "projection": // the map projection to use
}
사양 생성에 필요한 계산을 캡슐화하는 새 모듈을 만듭니다. 이 모듈은 사각형 형상을 사용하여 영역을 인쇄하고 결과를 자동으로 다운로드하는 printAndDownload 함수를 공개합니다.

인쇄하다.js


import { toLonLat } from "ol/proj";
import { getDistance } from "ol/sphere";
import { downloadBlob, print } from "@camptocamp/inkmap";

// more details on layers configuration later on
const bgLayer = {
  // ...
};

const trailsLayer = {
  // ..
};

/**
 * Requests a print from inkmap, download the resulting image
 * @param {Polygon} rectangleGeometry
 */
export function printAndDownload(rectangleGeometry) {
  // first get the geometry center in longitude/latitude
  const geomExtent = rectangleGeometry.getExtent();
  const center = toLonLat(
    rectangleGeometry.getInteriorPoint().getCoordinates()
  );

  // let's target a final format of A4:
  // the map will be 277 x 170 millimeters
  const size = [277, 170, 'mm'];

  // now the hard part: compute the scale denominator, which
  // is the ratio between the rectangle size in real world units
  // and the final printed size in the same units;
  // to do this we measure the width of the rectangle in
  // meters and compare it to the desired paper size
  const lowerLeft = toLonLat([geomExtent[0], geomExtent[1]]);
  const lowerRight = toLonLat([geomExtent[2], geomExtent[1]]);
  const geomWidthMeters = getDistance(lowerLeft, lowerRight);
  // paper size is in mm so we need to multiply by 1000!
  const scale = geomWidthMeters * 1000 / size[0];

  // let's print!
  print({
    layers: [bgLayer, trailsLayer],
    dpi: 150,
    size,
    center,
    scale,
    projection: 'EPSG:2056',
    scaleBar: true,
    northArrow: true
  }).then(imageBlob =>
    downloadBlob(imageBlob, 'hiking-trails.png')
  );
}
inkmap에 보내는 규범의 scale 파라미터를 어떻게 계산하는지 보셨습니까?이 매개 변수는 사실상 비례 분모이다. 다시 말하면 직사각형의 실제 사이즈 (수백 미터 가능) 와 최종 인쇄 사이즈 (A4 용지 한 장) 사이의 비율이다.
일단 우리가 비례를 계산해 내면 나머지는 매우 쉽다.그런데 잠깐만, 우리가 뭘 놓쳤잖아.아, 네, 층계!나는 앞의 목록에서 그것들을 생략했으니, 지금 우리 토론을 좀 하자.

데이터 소스 구성


Swisstopo는 WMTS (Web Map Tile Service)을 포함한 다양한 형식의 지리적 공간 데이터를 발표합니다.이 형식은 사용하기 쉽지 않지만, 고도로 비뚤어진 웹 메르카토르를 사용하지 않고 적당한 스위스 투영에서 데이터를 조회할 수 있도록 해 준다.

See the previous paragraph? We specified projection: 'EPSG:2056' in the print spec, just for that. inkmap will do the rest and download the projection definition on its own.


다음과 같이 레이어를 구성합니다.

인쇄하다.js


// ...

// there are shared parameters for both layers
// including resolutions, tile grid origin and matrix set
const genericLayer = {
  type: 'WMTS',
  requestEncoding: 'REST',
  matrixSet: 'EPSG:2056',
  projection: 'EPSG:2056',
  tileGrid: {
    resolutions: [
      4000, 3750, 3500, 3250, 3000, 2750, 2500, 2250, 2000,
      1750, 1500, 1250, 1000, 750, 650, 500, 250, 100, 50, 20
    ],
    origin: [2420000, 1350000]
  },
  attribution: '© Swisstopo'
};

// use the parameters above and target a background layer
const bgLayer = {
  ...genericLayer,
  url: 'https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.pixelkarte-farbe/default/current/2056/{TileMatrix}/{TileCol}/{TileRow}.jpeg',
  opacity: 0.4,
};

// this targets specifically the hiking trails layer
const trailsLayer = {
  ...genericLayer,
  url: 'https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.swisstlm3d-wanderwege/default/current/2056/{TileMatrix}/{TileCol}/{TileRow}.png',
};

// ...
WMTS 도면층은 축소 단계의 해상도 배열, 배열 격자 원점, 행렬 집합 id, 때로는 다른 매개 변수를 포함하여 적당한 배열 격자 설정을 정확하게 표시해야 한다.이 점을 설정하는 것이 매우 중요합니다. 본고의 목적을 위해 저는 existing examples(geo.admin.ch API doc)에서 영감을 얻었습니다.

그것을 한데 묶다


우리 거의 다 왔어!주 모듈에서 새로운 printAndDownload 함수를 사용하겠습니다.

응용 프로그램.js


// add this import at the top
import { printAndDownload } from './print';

// ...

// bind the print button click handler
document.getElementById('print-btn')
  .addEventListener('click', () => {
    printAndDownload(rectangle.getGeometry());
  });
이제 응용 프로그램으로 돌아갑니다.시각적으로 아무런 변화가 없지만, "인쇄"단추를 누르고 몇 초를 기다리면...펑!인쇄된 지도를 받았습니다. 다음과 같습니다.

이태리 마카로니
그것은 미국의 대부분 지역을 덮고 있기 때문에 가독성이 좋지 않지만, 당신은 틀림없이 비교적 작은 지역을 선택한 후에 다시 인쇄할 수 있을 것이다.봐라, 왼쪽 아래에 비례자가 하나 있다!!
이미지가 좋은데...우리는 진정한 PDF 문서를 인쇄할 수 있습니까?그럼 다행이다!

일을 끝내다


PDF 문서를 생성하기 위해 런타임 종속 항목(마지막 희망)을 추가로 도입합니다. jsPDF:
$ npm install --save jspdf
print 모듈에서 이 새 장난감을 사용하겠습니다.

인쇄하다.js


// add this import at the top
import { jsPDF } from "jspdf";

// ...

export function printAndDownload(rectangleGeometry) {

  // ...

  // let's print!
  print({
    // ...
  }).then(imageBlob => {
    // initializes the PDF document
    const doc = new jsPDF({
      orientation: 'landscape',
      unit: 'mm',
      format: 'a4',
      putOnlyUsedFonts: true,
    });

    // create an Object URL from the map image blob
    // and add it to the PDF
    const imgUrl = URL.createObjectURL(imageBlob);
    doc.addImage(imgUrl, 'JPEG', 10, 30, size[0], size[1]);

    // add a title
    doc.setFont('times', 'bold');
    doc.setFontSize(20);
    doc.text('This is going to be great.', 148.5, 15, null, null, 'center');

    // download the result
    doc.save('hiking-trails.pdf');
  });
}
지금 당신은 "인쇄"를 클릭하면 진정한 PDF 문서를 받을 수 있습니다.

응석받이로 자랐다.
너의 유일한 남은 것은 그것을 A4에 인쇄해서 너의 가방을 정리하고 너의 운명을 향해 가는 것이다.아니면...가장 가까운 버스 정류장.

결론


나는 이 문장이 일리가 있기를 희망하며, 네가 읽기와 실험에서 즐거움을 얻기를 바란다.지도를 인쇄하는 것은 결코 간단하지 않지만, 정확한 도구를 사용하여 정확한 작업을 완성할 때, 이 모든 것은 의미가 있다.
또한 본고에서 사용한 모든 소프트웨어는 원본이기 때문에 원하신다면 주저하지 말고 지역사회에 연락하여 도움을 주십시오!잘 쓴 버그 보고서도 큰 도움이 될 겁니다.
글에서 보여준 항목은 here을 찾을 수 있습니다. 만약 당신이 자신을 파괴하고 싶다면 live demo도 있습니다!
감사합니다.

좋은 웹페이지 즐겨찾기