Source: src/component/FeatureRenderer.js

  1. /* Copyright (c) 2015-2017 The Open Source Geospatial Foundation
  2. *
  3. * This program is free software: you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation, either version 3 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. */
  16. /**
  17. * A component that renders a `ol.style.Style` with an optional `ol.Feature`.
  18. *
  19. * @example preview
  20. * var poly = Ext.create('GeoExt.component.FeatureRenderer', {
  21. * symbolizers: new ol.style.Style({
  22. * fill: new ol.style.Fill({color: 'red'})
  23. * })
  24. * });
  25. * var line = Ext.create('GeoExt.component.FeatureRenderer', {
  26. * symbolizers: new ol.style.Style({
  27. * stroke: new ol.style.Stroke({color: 'orange', width: 3}),
  28. * }),
  29. * symbolType: 'Line'
  30. * });
  31. * var point = Ext.create('GeoExt.component.FeatureRenderer', {
  32. * symbolizers: new ol.style.Style({
  33. * image: new ol.style.Circle({
  34. * radius: 7,
  35. * fill: new ol.style.Fill({color: 'gray'}),
  36. * stroke: new ol.style.Stroke({color: 'black', width: 3}),
  37. * })
  38. * }),
  39. * symbolType: 'Point'
  40. * });
  41. * var star = Ext.create('GeoExt.component.FeatureRenderer', {
  42. * symbolizers: new ol.style.Style({
  43. * image: new ol.style.RegularShape({
  44. * fill: new ol.style.Fill({color: 'blue'}),
  45. * stroke: new ol.style.Stroke({color: 'green', width: 3}),
  46. * points: 7,
  47. * radius: 15,
  48. * radius2: 7,
  49. * angle: 0
  50. * })
  51. * }),
  52. * minWidth: 40,
  53. * minHeight: 40,
  54. * symbolType: 'Point'
  55. * });
  56. * Ext.create('Ext.panel.Panel', {
  57. * title: 'Rendering of ol.Features in a panel',
  58. * items: [poly, line, point, star],
  59. * border: false,
  60. * renderTo: Ext.getBody()
  61. * });
  62. *
  63. * @class GeoExt.component.FeatureRenderer
  64. */
  65. Ext.define('GeoExt.component.FeatureRenderer', {
  66. extend: 'Ext.Component',
  67. alias: 'widget.gx_renderer',
  68. requires: ['GeoExt.util.Version'],
  69. mixins: ['GeoExt.mixin.SymbolCheck'],
  70. // <debug>
  71. symbols: [
  72. 'ol.extent.getCenter',
  73. 'ol.extent.getWidth',
  74. 'ol.extent.getHeight',
  75. 'ol.Feature',
  76. 'ol.Feature#getGeometry',
  77. 'ol.Feature#setStyle',
  78. 'ol.geom.Geometry#getExtent',
  79. 'ol.geom.Point',
  80. 'ol.geom.LineString',
  81. 'ol.geom.Polygon',
  82. 'ol.layer.Vector',
  83. 'ol.layer.Vector#getSource',
  84. 'ol.Map#getSize',
  85. 'ol.Map#getView',
  86. 'ol.Map#setView',
  87. 'ol.Map#updateSize',
  88. 'ol.proj.Projection',
  89. 'ol.source.Vector',
  90. 'ol.source.Vector#addFeature',
  91. 'ol.View',
  92. 'ol.View#fit',
  93. ],
  94. // </debug>
  95. /**
  96. * Fires when the feature is clicked on.
  97. *
  98. * @event click
  99. * @param {GeoExt.component.FeatureRenderer} renderer The feature renderer.
  100. */
  101. config: {
  102. /**
  103. * Optional class to set on the feature renderer div.
  104. *
  105. * @cfg {string}
  106. */
  107. imgCls: '',
  108. /**
  109. * The minimum width.
  110. *
  111. * @cfg {number}
  112. */
  113. minWidth: 20,
  114. /**
  115. * The minimum height.
  116. *
  117. * @cfg {number}
  118. */
  119. minHeight: 20,
  120. /**
  121. * The resolution for the renderer.
  122. *
  123. * @cfg {number}
  124. */
  125. resolution: 1,
  126. /**
  127. * Optional vector to be drawn.
  128. *
  129. * @cfg {ol.Feature}
  130. */
  131. feature: undefined,
  132. /**
  133. * Feature to use for point swatches. Optional.
  134. *
  135. * @cfg {ol.Feature}
  136. */
  137. pointFeature: undefined,
  138. /**
  139. * Feature to use for line swatches. Optional.
  140. *
  141. * @cfg {ol.Feature}
  142. */
  143. lineFeature: undefined,
  144. /**
  145. * Feature to use for polygon swatches. Optional.
  146. *
  147. * @cfg {ol.Feature}
  148. */
  149. polygonFeature: undefined,
  150. /**
  151. * Feature to use for text label swatches. Optional.
  152. *
  153. * @cfg {ol.Feature}
  154. */
  155. textFeature: undefined,
  156. /**
  157. * An `ol.style.Style` instance or an array of `ol.style.Style`
  158. * instances for rendering a feature. If no symbolizers are
  159. * provided, the default style from OpenLayers will be used.
  160. *
  161. * @cfg {Array<ol.style.Style> | ol.style.Style}
  162. */
  163. symbolizers: undefined,
  164. /**
  165. * One of `"Point"`, `"Line"`, `"Polygon"` or `"Text"`. Only relevant
  166. * if `feature` is not provided.
  167. *
  168. * @cfg {string}
  169. */
  170. symbolType: 'Polygon',
  171. },
  172. inheritableStatics: {
  173. /**
  174. * Determines the style for the given feature record.
  175. *
  176. * @param {GeoExt.data.model.Feature} record A feature record to get the
  177. * styler for.
  178. * @return {Array<ol.style.Style> | ol.style.Style} The style(s) applied to the
  179. * given feature record.
  180. */
  181. determineStyle: function (record) {
  182. const feature = record.getFeature();
  183. return (
  184. feature.getStyle() ||
  185. feature.getStyleFunction() ||
  186. (record.store ? record.store.layer.getStyle() : null)
  187. );
  188. },
  189. },
  190. /**
  191. * Initialize the GeoExt.component.FeatureRenderer.
  192. */
  193. initComponent: function () {
  194. const me = this;
  195. const id = me.getId();
  196. me.autoEl = {
  197. id: id,
  198. tag: 'div',
  199. class: this.getImgCls(),
  200. };
  201. if (!me.getLineFeature()) {
  202. me.setLineFeature(
  203. new ol.Feature({
  204. geometry: new ol.geom.LineString([
  205. [-8, -3],
  206. [-3, 3],
  207. [3, -3],
  208. [8, 3],
  209. ]),
  210. }),
  211. );
  212. }
  213. if (!me.getPointFeature()) {
  214. me.setPointFeature(
  215. new ol.Feature({
  216. geometry: new ol.geom.Point([0, 0]),
  217. }),
  218. );
  219. }
  220. if (!me.getPolygonFeature()) {
  221. me.setPolygonFeature(
  222. new ol.Feature({
  223. geometry: new ol.geom.Polygon([
  224. [
  225. [-8, -4],
  226. [-6, -6],
  227. [6, -6],
  228. [8, -4],
  229. [8, 4],
  230. [6, 6],
  231. [-6, 6],
  232. [-8, 4],
  233. ],
  234. ]),
  235. }),
  236. );
  237. }
  238. if (!me.getTextFeature()) {
  239. me.setTextFeature(
  240. new ol.Feature({
  241. geometry: new ol.geom.Point([0, 0]),
  242. }),
  243. );
  244. }
  245. me.map = new ol.Map({
  246. controls: [],
  247. interactions: [],
  248. layers: [
  249. new ol.layer.Vector({
  250. source: new ol.source.Vector(),
  251. }),
  252. ],
  253. });
  254. const feature = me.getFeature();
  255. if (!feature) {
  256. me.setFeature(me['get' + me.getSymbolType() + 'Feature']());
  257. } else {
  258. me.applyFeature(feature);
  259. }
  260. me.callParent();
  261. },
  262. /**
  263. * Draw the feature when we are rendered.
  264. *
  265. * @private
  266. */
  267. onRender: function () {
  268. this.callParent(arguments);
  269. this.drawFeature();
  270. },
  271. /**
  272. * After rendering we setup our own custom events using #initCustomEvents.
  273. *
  274. * @private
  275. */
  276. afterRender: function () {
  277. this.callParent(arguments);
  278. this.initCustomEvents();
  279. },
  280. /**
  281. * (Re-)Initializes our custom event listeners, mainly #onClick.
  282. *
  283. * @private
  284. */
  285. initCustomEvents: function () {
  286. const me = this;
  287. me.clearCustomEvents();
  288. me.el.on('click', me.onClick, me);
  289. },
  290. /**
  291. * Unbinds previously bound listeners on #el.
  292. *
  293. * @private
  294. */
  295. clearCustomEvents: function () {
  296. const el = this.el;
  297. if (el && el.clearListeners) {
  298. el.clearListeners();
  299. }
  300. },
  301. /**
  302. * Bound to the click event on the #el, this fires the click event.
  303. *
  304. * @private
  305. */
  306. onClick: function () {
  307. this.fireEvent('click', this);
  308. },
  309. /**
  310. * Private method called during the destroy sequence.
  311. *
  312. * @private
  313. */
  314. beforeDestroy: function () {
  315. const me = this;
  316. me.clearCustomEvents();
  317. if (me.map) {
  318. me.map.setTarget(null);
  319. }
  320. },
  321. /**
  322. * When resizing has happened, we might need to re-set the renderer's
  323. * dimensions via #setRendererDimensions.
  324. *
  325. * @private
  326. */
  327. onResize: function () {
  328. this.setRendererDimensions();
  329. this.callParent(arguments);
  330. },
  331. /**
  332. * Draw the feature in the map.
  333. *
  334. * @private
  335. */
  336. drawFeature: function () {
  337. const me = this;
  338. me.map.setTarget(me.el.id); // TODO why not me.el?
  339. me.setRendererDimensions();
  340. },
  341. /**
  342. * Set the dimension of our renderer, i.e. map and view.
  343. *
  344. * @private
  345. */
  346. setRendererDimensions: function () {
  347. const me = this;
  348. const gb = me.feature.getGeometry().getExtent();
  349. const gw = ol.extent.getWidth(gb);
  350. const gh = ol.extent.getHeight(gb);
  351. /*
  352. * Determine resolution based on the following rules:
  353. * 1) always use value specified in config
  354. * 2) if not specified, use max res based on width or height of element
  355. * 3) if no width or height, assume a resolution of 1
  356. */
  357. let resolution = me.initialConfig.resolution;
  358. if (!resolution) {
  359. resolution = Math.max(gw / me.width || 0, gh / me.height || 0) || 1;
  360. }
  361. me.map.setView(
  362. new ol.View({
  363. minResolution: resolution,
  364. maxResolution: resolution,
  365. projection: new ol.proj.Projection({
  366. code: '',
  367. units: 'pixels',
  368. }),
  369. }),
  370. );
  371. // determine height and width of element
  372. const width = Math.max(me.width || me.getMinWidth(), gw / resolution);
  373. const height = Math.max(me.height || me.getMinHeight(), gh / resolution);
  374. // determine bounds of renderer
  375. const center = ol.extent.getCenter(gb);
  376. const bhalfw = (width * resolution) / 2;
  377. const bhalfh = (height * resolution) / 2;
  378. const bounds = [
  379. center[0] - bhalfw,
  380. center[1] - bhalfh,
  381. center[0] + bhalfw,
  382. center[1] + bhalfh,
  383. ];
  384. me.el.setSize(Math.round(width), Math.round(height));
  385. me.map.updateSize();
  386. // Check for backwards compatibility
  387. if (GeoExt.util.Version.isOl3()) {
  388. me.map.getView().fit(bounds, me.map.getSize());
  389. } else {
  390. me.map.getView().fit(bounds);
  391. }
  392. },
  393. /**
  394. * We're setting the symbolizers on the feature.
  395. *
  396. * @param {Array<ol.style.Style> | ol.style.Style} symbolizers The style (or
  397. * array of styles) that have been set.
  398. * @return {Array<ol.style.Style> | ol.style.Style} The style (or
  399. * array of styles) that have been set.
  400. * @private
  401. */
  402. applySymbolizers: function (symbolizers) {
  403. const feature = this.getFeature();
  404. if (feature && symbolizers) {
  405. feature.setStyle(symbolizers);
  406. }
  407. return symbolizers;
  408. },
  409. /**
  410. * We're setting the feature and add it to the source.
  411. *
  412. * @param {ol.Feature} feature The feature that has been set.
  413. * @return {ol.Feature} feature The feature that has been set.
  414. * @private
  415. */
  416. applyFeature: function (feature) {
  417. const symbolizers = this.getSymbolizers();
  418. if (feature && symbolizers) {
  419. feature.setStyle(symbolizers);
  420. }
  421. if (this.map) {
  422. const source = this.map.getLayers().item(0).getSource();
  423. source.clear();
  424. source.addFeature(feature);
  425. }
  426. return feature;
  427. },
  428. /**
  429. * Update the `feature` or `symbolizers` and redraw the feature.
  430. *
  431. * Valid options:
  432. *
  433. * @param {Object} options Object with properties to be updated.
  434. * @param {ol.Feature} options.feature The new or updated feature.
  435. * @param {Array<ol.style.Style> | ol.style.Style} options.symbolizers The
  436. * symbolizers.
  437. */
  438. update: function (options) {
  439. if (options.feature) {
  440. this.setFeature(options.feature);
  441. }
  442. if (options.symbolizers) {
  443. this.setSymbolizers(options.symbolizers);
  444. }
  445. },
  446. });