Давайте сделаем масштабную карту

Примечание. Это первая часть серии статей, посвященных созданию картографических веб-приложений.

Картография - это причина, по которой я в первую очередь занялся программированием. Но я всегда сталкивался с ограничениями в первых созданных мной картографических приложениях. Добавление новых функций в мои проекты jQuery + LeafletJS превратилось в кошмар. Итак, я выбрал фреймворк приложения, который поможет мне еще немного упорядочить мой код.

Я выбрал EmberJS, потому что он включает в себя полный набор всего, что мне нужно для создания приложения. Однако во многих сообщениях блога на EmberJS мне не хватало этой уникальной комбинации сопоставления и разработки приложений. Это руководство пытается охватить:

  • Основы приложения EmberJS
  • Обещания, выборка и ответы в JavaScript
  • Привязки свойств MapboxGL и Ember

Создайте новое приложение

Приступим: сначала запустите npm install -g ember-cli в окне Терминала. Возможно, вам сначала понадобится npm. После этого вы можете запустить пустое приложение, запустив ember new map-demo-app. Это начнется с чистого листа и будет включать в себя основные структурные объекты приложения и всю полезную атрибутику Ember.

Запустите cd map-demo-app, а затем ember s. Если вы посетите http: // localhost: 4200 /, вы должны увидеть Tomster рядом с дружественным сообщением. Давайте продолжим работу этого сервера, откроем новое окно терминала, а затем откроем каталог map-demo-app в вашем любимом редакторе кода.

Структура папок EmberJS разделяет разные объекты в соответствии с их обязанностями. Например, помощники - это в основном простые функции, которые вы можете вызывать в любом месте разметки, обычно для форматирования чисел или дат.

Маршруты отвечают за определение поведения в зависимости от того, где мы находимся в URL-адресе. Если вы посмотрите URL-адрес прямо сейчас, вы увидите что-то вроде «medium.com/nycplanninglabs/blog-post-title». Это говорит нам о том, что мы читаем сообщение в блоге организации nycplanninglabs на веб-сайте Medium. Он также сообщает приложению, что нужно выкопать, чтобы показать пользователю.

Компоненты - большая часть EmberJS. Это настраиваемые, составляемые, многократно используемые, автономные части интерфейса. Они помогают организовать обязанности приложения в логические группы. С эргономической точки зрения они также создают ощущение инкапсуляции, чтобы другие могли позже взглянуть на ваш код и понять, что происходит.

Для нашей карты мы начнем с простого и определим новый маршрут, чтобы мы могли определить желаемое поведение при первом входе пользователя в приложение. В Ember, когда пользователь посещает /, корень нашего приложения, он переходит на маршрут приложения. В общем, вы можете называть свои маршруты как хотите и изменять то, как они косметически представлены в фактическом URL-адресе, но в этом случае маршрут приложения всегда является именем, которое Ember использует для самого верхнего уровня наших маршрутов.

Настроить основной маршрут

Запустите ember g route application. Когда мы запускали ember new ранее, ember-cli пошел дальше и создал для нас файл шаблона для маршрута приложения, поэтому ember-cli спросит, хотите ли вы его перезаписать. Вы можете просто нажать n, чтобы ответить нет. После этого команда должна создать для нас route файл с именем application.js. Обратите внимание, как имя файла JavaScript совпадает с именем файла шаблона - это соглашение в EmberJS, которое сообщает нашему компилятору, что эти две вещи идут вместе.

В этом проекте маршрут приложения будет отвечать за выборку данных для нашей карты. Практически в любом приложении Ember маршрут отвечает за подготовку своего шаблона для презентации. Маршруты в EmberJS включают ряд ловушек - думайте о них как о событиях, которые запускаются в определенные моменты жизненного цикла приложения. Хотите запустить что-нибудь после загрузки данных? Для этого есть крючок.

Получение данных в приложение

Откройте ваш application.js файл маршрута и добавьте метод для хука модели:

// routes/application.js
import Route from '@ember/routing/route';
export default Route.extend({
  model() {
    return {};
  }
});

Модельный хук - это хук маршрута с поддержкой Promise, означающий, что все, что находится после него, гарантированно будет запущено после разрешения Promise. Например, если мы используем обработчик модели для получения некоторых данных из GitHub, мы можем быть уверены, что наш шаблон не будет отображаться, пока эти данные не будут загружены. Это полезно, потому что теперь нам легче рассуждать об асинхронной природе JavaScript.

Обратите внимание, что в нашем коде выше мы просто возвращаем пустой литерал объекта. Если вы откроете application.hbs и напишете {{log model}}, вы увидите пустой литерал объекта в консоли JavaScript. Это говорит нам о том, что у нас есть доступ к нашим данным в шаблоне маршрута приложения через свойство model. Это также говорит нам, что мы должны загрузить в приложение реальные данные.

Начнем с некоторых данных о велосипедных дорожках в Чикаго, которые я нашел. GitHub достаточно хорош, чтобы создать для нас предварительный просмотр:

Чтобы получить эти строковые данные, с нашей обработкой модели должны произойти две вещи: во-первых, нам нужен способ, чтобы браузер выполнял сетевой запрос данных. Во-вторых, нам нужно знать, где на самом деле хранятся данные в сети.

Перво-наперво. Мы собираемся использовать Fetch API, новый стандарт для получения данных в браузере. Оно пока не поддерживается Internet Explorer, поэтому давайте запустим ember install ember-fetch, который гарантирует, что все пользователи смогут запускать это приложение, даже если они используют IE. Наконец, давайте импортируем его в наш файл маршрута:

import Route from '@ember/routing/route';
import fetch from 'fetch';
export default Route.extend({
  model() {
    return {};
  }
});

Во-вторых, давайте выясним, где находятся данные. Если мы откроем GitHub Gist и нажмем кнопку Raw, мы сможем скопировать URL-адрес и использовать его в качестве первого аргумента функции fetch:

import Route from '@ember/routing/route';
import fetch from 'fetch';
export default Route.extend({
  model() {
    return fetch('https://gist.githubusercontent.com/allthesignals/f9224f5b2860e19c900180f295f0dbe7/raw/ad81c60619ee95932dd6957a22a1a8d8b0e6a3b8/tutorial_chicago_bike_routes.geojson');
  }
});

Это немного некрасиво. Давайте переместим эту первую часть URL в константу с именем HOST:

import Route from '@ember/routing/route';
import fetch from 'fetch';
const HOST = 'https://gist.githubusercontent.com/allthesignals/f9224f5b2860e19c900180f295f0dbe7/raw/ad81c60619ee95932dd6957a22a1a8d8b0e6a3b8';
export default Route.extend({
  model() {
    return fetch(`${HOST}/tutorial_chicago_bike_routes.geojson`);
  }
});

Так немного лучше. Предположим, вы добавили {{log model}} в application.hbs, мы должны увидеть что-то новое в консоли:

Это называется объект ответа - Fetch API может запрашивать множество различных типов данных, включая изображения и видео, поэтому нам нужно указать fetch, как обрабатывать эти данные. В нашем случае нам просто нужен JSON:

import Route from '@ember/routing/route';
import fetch from 'fetch';
const HOST = 'https://gist.githubusercontent.com/allthesignals/f9224f5b2860e19c900180f295f0dbe7/raw/ad81c60619ee95932dd6957a22a1a8d8b0e6a3b8';
export default Route.extend({
  model() {
    return fetch(`${HOST}/tutorial_chicago_bike_routes.geojson`)
      .then(blob => blob.json());
  }
});

Если вы посмотрите в консоль JavaScript, вы должны увидеть что-то более знакомое:

Вот что делает ловушку модели Route такой красивой - очень легко рассуждать о нашем приложении, когда мы точно знаем, что наши данные доступны.

Отображение данных

Теперь, когда у нас есть данные, давайте что-нибудь с ними сделаем. Мне нравится использовать для этого MapboxGL, потому что он обеспечивает превосходное взаимодействие с пользователем. Прежде чем приступить непосредственно к проекту и добавить библиотеку MapboxGL в проект, я хотел бы заранее провести небольшое исследование, чтобы узнать, написали ли другие какие-либо привязки или интеграции для проблемы, которую я пытаюсь решить. Мы можем использовать ember-mapbox-gl, чтобы упростить реализацию нашей карты. Зачем нам все это? По сути, нам нужно что-то, что будет обрабатывать управление состоянием между EmberJS и MapboxGL. Если я передаю свойство стиля карте MapboxGL в Ember, мне не нужно вызывать setStyle методы в MapboxGL - Ember уже выполняет привязку атрибутов за нас. По этой причине я всегда стараюсь найти дополнение для интеграции.

Начните с запуска ember install ember-mapbox-gl. Это установит аддон. Возможно, вам придется перезагрузить сервер. Затем возьмите API-ключ из Mapbox. Этот ключ API является открытым ключом, используемым для базовых карт. Этот шаг можно пропустить и использовать собственную базовую карту, но мы рассмотрим это в следующем руководстве. Добавьте этот ключ API в свой config/environment.js файл:

// config/environment.js
'mapbox-gl': {
  accessToken: 'ACCESS TOKEN HERE',
  map: {
    style: 'mapbox://styles/mapbox/basic-v9',
    zoom: 8,
    center: [ -87.4985112, 41.8832547 ]
  }
},

Это устанавливает положение карты по умолчанию для всех видов использования карт mapbox-gl. Взгляните на свой application.hbs файл и удалите {{welcome-page}} (это то, что отображает изображение Томстера). Теперь давайте добавим компонент карты и углубимся в детали:

Здесь много чего происходит. Некоторые из них требуют базового понимания JavaScript API MapboxGL. При создании карт в вашем распоряжении множество различных типов объектов, но по сути, эти карты состоят из источников и слоев. Источники управляют данными, отображаемыми на экране - если вы используете растровые или мозаичные данные, API MapboxGL поможет вам. Слои управляют логикой представления того, что отображается на экране, и ссылаются на загруженный идентификатор источника.

В строке 3 мы вызываем только что установленный mapbox-gl компонент и передаем ему блок. Подобно тегам HTML, это функционально контекстуализирует внутренние детали всего, что передается. Иногда он просто помещает его в другой тег div, в других случаях он предоставляет другие функции, как в строке 4, где мы видим следующее:

{{#map.source options=...}}

Здесь мы определяем новый mapbox gl source в экземпляре карты. Связь между экземпляром карты и исходным объектом устанавливается контекстным параметром, обозначенным в строке 3 как {{ ... as |map|}}.

Вы также заметите, что в строке 4 мы говорим data=model. Здесь мы передаем данные разрешенной модели в исходный компонент mapbox-gl. Наконец, мы вызываем компонент layer с {{source.layer ...}} в строке 5. В MapboxGL источники должны существовать внутри карты, прежде чем слои смогут их вызывать. Вложенный API шаблона здесь помогает проиллюстрировать это.

Наконец, если вы посмотрите на свое приложение в браузере, вы все равно увидите пустую страницу. Вы можете исправить это, добавив CSS для увеличения ширины и высоты карты. Откройте app/styles/app.css и добавьте следующую строку:

.mapboxgl-map {  height: 100vh;  width: 100vw;}

Итак, мы идем:

Что теперь?

Мы заложили основу для создания амбициозной карты. Наша архитектура приложения поможет нам организовать наш код для более устойчивого проекта.

На следующей неделе давайте добавим несколько эффектов наведения, включая выделение и всплывающие окна. Мы также добавим фильтры. Дайте мне знать в комментариях ниже, если у вас есть вопросы!