0

I'm trying to work through the tutorial here*. But I'm discovering something unexpected in terms of my map component.

Background info on the app/installation:

I installed GeoExt3 by cloning this repository: https://github.com/geoext/geoext3

The application began as a universal app: https://github.com/geoext/geoext3/blob/master/universal-app.md

My app's mapComponent extends GeoExt.component.Map which extends Ext.Component. getWidth() is an Ext.Component method. I wonder if my issue may be related to this inheritance. The following two api docs had me confused for a second, but then realized that the first is GeoExt without ext components, and the second includes ext components.

https://geoext.github.io/geoext3/master/docs/#!/api/GeoExt.component.Map http://geoext.github.io/geoext3/master/docs-w-ext/#!/api/GeoExt.component.Map

The app code here.

My layers, maps, and map component are created (successfully) in this way:

var t_coupureaerien_source = new ol.source.VectorTile({
  format: new ol.format.MVT(),
  url: 'https://ahocevar.com/geoserver/gwc/service/tms/1.0.0/' + 'ne:ne_10m_admin_0_countries@EPSG%3A900913@pbf/{z}/{x}/{-y}.pbf'
})

var t_coupureaerien_style = new ol.style.Style({
  fill: new ol.style.Fill({color: 'rgba(255, 255, 255, 0.6)'}),
  stroke: new ol.style.Stroke({color: '#319FD3', width: 1})
});

var t_coupureaerien_layer = new ol.layer.VectorTile({
  title: 'Coupure Aerien HTA',
  style: t_coupureaerien_style,
  source: t_coupureaerien_source,
  legendUrl: 'https://ahocevar.com/geoserver/gwc/service/tms/1.0.0/' + 'ne:ne_10m_admin_0_countries@EPSG%3A900913@pbf/{z}/{x}/{-y}.pbf',
  name: 'Appareil de coupure aerien'
});

var osm_source = new ol.source.OSM({url: "https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"});
var osm_basemap = new ol.layer.Tile({
  title: 'OSM basemap',
  source: osm_source,
  //legendUrl: 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
  name: 'OSM basemap'})

var extentLayer = new ol.layer.Vector({
  source: new ol.source.Vector()
});

var view = new ol.View({
  center: ol.proj.fromLonLat([-149.3, -17.7]),
  zoom: 7
});

var my_map = new ol.Map({
  controls: ol.control.defaults().extend([new ol.control.ScaleLine]),
  target: 'map',
  view: view,
  layers: [
    new ol.layer.Group({title: 'Fond de plans', layers: [osm_basemap], name: 'Fond de plans'}),
    new ol.layer.Group({title: 'Réseau Secosud', layers: [coupureaerien_layer], name: 'Réseau Secosud'}),
    extentLayer
  ],
  //overlays: [overlay],
});

var mapComponent = Ext.define('SIG.view.main.Map', {
  // extend: "Ext.panel.Panel",
  extend: 'GeoExt.component.Map',
  xtype: 'mappanel',
  //region: 'center',
  requires: [
    'SIG.view.main.MapController', 'SIG.view.main.MapModel'
  ],

  controller: 'main-map',
  viewModel: {
    type: 'main-map'
  },
  // html: "Hello, World!!"
  map: my_map
});

console.log(mapComponent);
console.log(mapComponent.getWidth());

But getWidth() gives this error despite it being a method of the map component :

Uncaught TypeError: mapComponent.getWidth is not a function

I've printed the mapComponent object to the console and the superclass shows the following methods. Seems like getWidth is not there. Is this part of the issue? (Excuse me for the json formatting...i could not get firefox's toSource to work). First thought is that it might be, but then again my object extends GeoExt.map.Component which in turn extends Ext.Component, so this may not be printing the entire chain (?).

superclass: {…}

​​

"$className": "GeoExt.component.Map"

​​

"$inheritableStatics": Object { _checked: true, check: true, normalizeSymbol: true, … }

​​

"$noClearOnDestroy": Object { events: true, hasListeners: true, managedListeners: true, … }

​​

HasListeners: function HasListeners()

​​

addLayer: function addLayer()

​​

alias: Array [ "widget.gx_map", "widget.gx_component_map" ]

​​

applyPointerRest: function applyPointerRest()

​​

applyPointerRestInterval: function applyPointerRestInterval()

​​

applyState: function applyState()

​​

bindOverOutListeners: function bindOverOutListeners()

​​

bindStateOlEvents: function bindStateOlEvents()

​​

bufferedPointerMove: function emptyFn()

​​

childEls: Object { frameTable: {}, frameTL: {}, frameTC: {}, … }

​​

config: Object { pointerRest: false, pointerRestInterval: 1000, pointerRestPixelTolerance: 3, … }

​​

constructor: function constructor()

​​

defaultConfig: Object { pointerRest: false, pointerRestInterval: 1000, pointerRestPixelTolerance: 3, … }

​​

defaultListenerScope: false

​​

getCenter: function getCenter()

​​

getExtent: function getExtent()

​​

getLayers: function getLayers()

​​

getMap: function makeGetter()

​​

getPointerRest: function makeGetter()

​​

getPointerRestInterval: function makeGetter()

​​

getPointerRestPixelTolerance: function makeGetter()

​​

getState: function getState()

​​

getStore: function getStore()

​​

getView: function getView()

​​

isMouseOverMapEl: null

​​

lastPointerPixel: null

​​

layerStore: null

​​

map: null

​​

mapRendered: false

​​

mixins: Object { isDefinedSymbol: {…} }

​​

onMouseOut: function onMouseOut()

​​

onMouseOver: function onMouseOver()

​​

onResize: function onResize()

​​

pointerRestPixelTolerance: 3

​​

registerPointerRestEvents: function registerPointerRestEvents()

​​

removeLayer: function removeLayer()

​​

requires: Array [ Layers()

, Version()

]

​​

self: function Map()

​​

setCenter: function setCenter()

​​

setExtent: function setExtent()

​​

setMap: function setter()

​​

setPointerRest: function setter()

​​

setPointerRestInterval: function setter()

​​

setPointerRestPixelTolerance: function setter()

​​

setView: function setView()

​​

stateEvents: Array [ "aftermapmove" ]

​​

superclass: Object { self: Component()

, superclass: {…}, defaultConfig: {…}, … }

​​

symbols: Array(15) [ "ol.layer.Base", "ol.Map", "ol.Map#addLayer", … ]

​​

unbindOverOutListeners: function unbindOverOutListeners()

​​

unbufferedPointerMove: function unbufferedPointerMove()

​​

unregisterPointerRestEvents: function unregisterPointerRestEvents()

​​

xtype: "gx_map"

​​

xtypes: Array [ "gx_map", "gx_component_map" ]

​​

xtypesChain: Array(4) [ "component", "box", "gx_map", … ]

​​

xtypesMap: Object { component: true, box: true, gx_map: true, … }

​​

<prototype>: Object { self: Component()

, superclass: {…}, defaultConfig: {…}, … }

This is called by the MapFish module, therefore, using the following code gives me the same error:

Ext.require([
  'GeoExt.component.Map',
  'SIG.view.main.Map']);
/**
  * Once the store is loaded, we can create the button with the
  * following assumptions:
  *
  *     * The button will print the first layout
  *     * The attributes used are the first of the above layout
  *     * We'll request the first dpi value of the suggested ones
  * @param {GeoExt.data.MapfishPrintProvider} provider The print
  *     provider.
  */
 var onPrintProviderReady = function(provider) {
     // this is the assumption: take the first layout and render an
     // appropriate extent on the map
     var capabilities = provider.capabilityRec;
     var layout = capabilities.layouts().getAt(0);
     var attr = layout.attributes().getAt(0);
     var clientInfo = attr.get('clientInfo');
     var render = GeoExt.data.MapfishPrintProvider.renderPrintExtent;
     console.log(mapComponent);
     render(mapComponent, extentLayer, clientInfo);

     mapComponent.getView().on('propertychange', function() {
         extentLayer.getSource().clear();
         render(mapComponent, extentLayer, clientInfo);
     });
     description.add({
         xtype: 'button',
         text: 'Print',
         handler: function() {
             var spec = {
                 layout: layout.get('name'),
                 attributes: {}
             };
             var firstFeature = extentLayer.getSource().getFeatures()[0];
             var bbox = firstFeature.getGeometry().getExtent();
             var util = GeoExt.data.MapfishPrintProvider;
             var mapView = mapComponent.getView();
             var serializedLayers = util.getSerializedLayers(
                 mapComponent,
                 function(layer) {
                     // do not print the extent layer
                     var isExtentLayer = (extentLayer === layer);
                     return !isExtentLayer;
                 }
             );
             serializedLayers = unHttpsLayers(serializedLayers);
             serializedLayers.reverse();
             spec.attributes[attr.get('name')] = {
                 bbox: bbox,
                 dpi: clientInfo.dpiSuggestions[0],
                 layers: serializedLayers,
                 projection: mapView.getProjection().getCode(),
                 rotation: mapView.getRotation()
             };
             Ext.create('Ext.form.Panel', {
                 standardSubmit: true,
                 url: 'https://apps.terrestris.de/print-servlet-3.1.2/' +
                     'print/geoext/buildreport.pdf',
                 method: 'POST',
                 items: [
                     {
                         xtype: 'textfield',
                         name: 'spec',
                         value: Ext.encode(spec)
                     }
                 ]
             }).submit();
         }
     });
 };

 Ext.create('GeoExt.data.MapfishPrintProvider', {
     url: 'https://apps.terrestris.de/print-servlet-3.1.2/' +
             'print/geoext/capabilities.json',
     listeners: {
         ready: onPrintProviderReady
     }
 });

For those who aren't familiar with the MapFishPrintProvider. The issue is in renderPrintExtent:

/* Copyright (c) 2015-2017 The Open Source Geospatial Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Provides an interface to a Mapfish or GeoServer print module.
 *
 * @class GeoExt.data.MapfishPrintProvider
 */
Ext.define('GeoExt.data.MapfishPrintProvider', {
    extend: 'Ext.Base',
    mixins: [
        'Ext.mixin.Observable',
        'GeoExt.mixin.SymbolCheck'
    ],
    requires: [
        'GeoExt.data.model.print.Capability',
        'Ext.data.JsonStore'
    ],
    // <debug>
    symbols: [
        'ol.Collection',
        'ol.geom.Polygon.fromExtent',
        'ol.Feature',
        'ol.layer.Layer#getSource',
        'ol.layer.Group',
        'ol.source.Vector.prototype.addFeature',
        'ol.View#calculateExtent'
    ],
    // </debug>

    /**
     * @event ready
     * Fires after the PrintCapability store is loaded.
     *
     * @param {GeoExt.data.MapfishPrintProvider} provider The
     *     GeoExt.data.MapfishPrintProvider itself
     */

    config: {
        capabilities: null,
        url: ''
    },

    inheritableStatics: {
        /**
         * An array of objects specifying a serializer and a connected
         * OpenLayers class. This should not be manipulated by hand, but rather
         * with the method #registerSerializer.
         *
         * @private
         */
        _serializers: [],

        /**
         * Registers the passed serializer class as an appropriate serializer
         * for the passed OpenLayers source class.
         *
         * @param {ol.source.Source} olSourceCls The OpenLayers source class
         *    that the passed serializer can serialize.
         * @param {GeoExt.data.serializer.Base} serializerCls The serializer
         *    that can serialize the passed source.
         */
        registerSerializer: function(olSourceCls, serializerCls) {
            var staticMe = GeoExt.data.MapfishPrintProvider;
            staticMe._serializers.push({
                olSourceCls: olSourceCls,
                serializerCls: serializerCls
            });
        },

        /**
         * Unregisters the passed serializer class from the array of available
         * serializers. This may be useful if you want to register a new
         * serializer that is different from a serializer that we provide.
         *
         * @param {GeoExt.data.serializer.Base} serializerCls The serializer
         *    that can serialize the passed source.
         * @return {Boolean} Whether we could unregister the serializer.
         */
        unregisterSerializer: function(serializerCls) {
            var available = GeoExt.data.MapfishPrintProvider._serializers;
            var index;
            Ext.each(available, function(candidate, idx) {
                if (candidate.serializerCls === serializerCls) {
                    index = idx;
                    return false; // break early
                }
            });
            if (Ext.isDefined(index)) {
                Ext.Array.removeAt(available, index);
                return true;
            }
            return false;
        },

        /**
         * Returns a GeoExt.data.serializer.Base capable of serializing the
         * passed source instance or undefined, if no such serializer was
         * previously registered.
         *
         * @param {ol.source.Source} source The source instance to find a
         *    serializer for.
         * @return {GeoExt.data.serializer.Base} A serializer for the passed
         *    source or `undefined`.
         */
        findSerializerBySource: function(source) {
            var available = GeoExt.data.MapfishPrintProvider._serializers;
            var serializer;

            Ext.each(available, function(candidate) {
                if (source instanceof candidate.olSourceCls) {
                    serializer = candidate.serializerCls;
                    return false; // break early
                }
            });
            if (!serializer) {
                Ext.log.warn('Couldn\'t find a suitable serializer for source.'
                    + ' Did you require() an appropriate serializer class?');
            }
            return serializer;
        },

        /**
         * Will return an array of ol-layers by the given collection. Layers
         * contained in `ol.layer.Group`s get extracted and groups get removed
         * from returning array
         *
         * @param {GeoExt.data.store.Layers|ol.Collection|ol.layer.Base[]} coll
         *     The 'collection' of layers to get as array. If passed as
         *     ol.Collection, all items must be `ol.layer.Base`.
         * @return {Array} The flat layers array.
         */
        getLayerArray: function(coll) {
            var me = this;
            var inputLayers = [];
            var outputLayers = [];

            if (coll instanceof GeoExt.data.store.Layers) {
                coll.each(function(layerRec) {
                    var layer = layerRec.getOlLayer();
                    inputLayers.push(layer);
                });
            } else if (coll instanceof ol.Collection) {
                inputLayers = Ext.clone(coll.getArray());
            } else {
                inputLayers = Ext.clone(coll);
            }

            inputLayers.forEach(function(layer) {
                if (layer instanceof ol.layer.Group) {
                    Ext.each(me.getLayerArray(layer.getLayers()),
                        function(subLayer) {
                            outputLayers.push(subLayer);
                        });
                } else {
                    outputLayers.push(layer);
                }
            });
            return outputLayers;
        },

        /**
         * Will return an array of serialized layers for mapfish print servlet
         * v3.0.
         *
         * @param {GeoExt.component.Map} mapComponent The GeoExt map component
         *     to get the the layers from.
         * @param {Function} [filterFn] A function to filter the layers to be
         *     serialized.
         * @param {ol.layer.Base} filterFn.item The layer to check for
         *     inclusion.
         * @param {Number} filterFn.index The index of the layer in the
         *     flattened list.
         * @param {Array} filterFn.array The complete flattened array of layers.
         * @param {Boolean} filterFn.return Return a truthy value to keep the
         *     layer and serialize it.
         * @param {Object} [filterScope] The scope in which the filtering
         *     function will be executed.
         * @return {Array<Object>} An array of serialized layers.
         * @static
         */
        getSerializedLayers: function(mapComponent, filterFn, filterScope) {
            var layers = mapComponent.getLayers();
            var viewRes = mapComponent.getView().getResolution();
            var serializedLayers = [];
            var inputLayers = this.getLayerArray(layers);

            if (Ext.isDefined(filterFn)) {
                inputLayers = Ext.Array.filter(
                    inputLayers, filterFn, filterScope
                );
            }

            Ext.each(inputLayers, function(layer) {
                var source = layer.getSource();
                var serialized = {};

                var serializer = this.findSerializerBySource(source);
                if (serializer) {
                    serialized = serializer.serialize(layer, source, viewRes);
                    serializedLayers.push(serialized);
                }
            }, this);

            return serializedLayers;
        },

        /**
         * Renders the extent of the printout. Will ensure that the extent is
         * always visible and that the ratio matches the ratio that clientInfo
         * contains.
         *
         * @param {GeoExt.component.Map} mapComponent The map component to
         *     render the print extent to.
         * @param {ol.layer.Vector} extentLayer The vector layer to render the
         *     print extent to.
         * @param {Object} clientInfo Information about the desired print
         *     dimensions.
         * @param {Number} clientInfo.width The target width.
         * @param {Number} clientInfo.height The target height.
         * @return {ol.Feature} The feature representing the print extent.
         */
        renderPrintExtent: function(mapComponent, extentLayer, clientInfo) {
            var mapComponentWidth = mapComponent.getWidth();
            var mapComponentHeight = mapComponent.getHeight();
            var currentMapRatio = mapComponentWidth / mapComponentHeight;
            var scaleFactor = 0.6;
            var desiredPrintRatio = clientInfo.width / clientInfo.height;
            var targetWidth;
            var targetHeight;
            var geomExtent;
            var feat;

            if (desiredPrintRatio >= currentMapRatio) {
                targetWidth = mapComponentWidth * scaleFactor;
                targetHeight = targetWidth / desiredPrintRatio;
            } else {
                targetHeight = mapComponentHeight * scaleFactor;
                targetWidth = targetHeight * desiredPrintRatio;
            }

            geomExtent = mapComponent.getView().calculateExtent([
                targetWidth,
                targetHeight
            ]);
            feat = new ol.Feature(ol.geom.Polygon.fromExtent(geomExtent));
            extentLayer.getSource().addFeature(feat);
            return feat;
        }
    },

    /**
     * The capabiltyRec is an instance of 'GeoExt.data.model.print.Capability'
     * and contans the PrintCapabilities of the Printprovider.
     *
     * @property
     * @readonly
     */
    capabilityRec: null,

    constructor: function(cfg) {
        this.mixins.observable.constructor.call(this, cfg);
        if (!cfg.capabilities && !cfg.url) {
            Ext.Error.raise('Print capabilities or Url required');
        }
        this.initConfig(cfg);
        this.fillCapabilityRec();
    },

    /**
     * Creates the store from object or url.
     *
     * @private
     */
    fillCapabilityRec: function() {
        // enhance checks
        var store;
        var capabilities = this.getCapabilities();
        var url = this.getUrl();
        var fillRecordAndFireEvent = function() {
            this.capabilityRec = store.getAt(0);
            if (!this.capabilityRec) {
                this.fireEvent('error', this);
            } else {
                this.fireEvent('ready', this);
            }
        };
        if (capabilities) { // if capability object is passed
            store = Ext.create('Ext.data.JsonStore', {
                model: 'GeoExt.data.model.print.Capability',
                listeners: {
                    datachanged: fillRecordAndFireEvent,
                    scope: this
                }
            });
            store.loadRawData(capabilities);
        } else if (url) { // if servlet url is passed
            store = Ext.create('Ext.data.Store', {
                autoLoad: true,
                model: 'GeoExt.data.model.print.Capability',
                proxy: {
                    type: 'jsonp',
                    url: url,
                    callbackKey: 'jsonp'
                },
                listeners: {
                    load: fillRecordAndFireEvent,
                    scope: this
                }
            });
        }
    }
});

My Main.js file:

 Ext.define('SIG.view.main.Main', {
   extend: 'Ext.container.Viewport',

   requires: [
     'Ext.plugin.Viewport', 'Ext.window.MessageBox', 'SIG.view.main.MainController', 'SIG.view.main.MainModel', 'SIG.view.main.List', 'SIG.view.main.Map', 'SIG.view.main.Print'
   ],
   controller: 'main',
   viewModel: 'main',
   layout: 'border',
   items: [
         mapComponent,
         layerTreePanel
   ]

 })

*Sort of side note: This app doesn't work for me, by the way, with an error: "Error while processing request: java.net.MalformedURLException: unknown protocol: data", but I've decided to attempt to work through it anyway.

EDIT

I've updated the code to create the MapFishPrintProvider using a listener on the map component render as suggested by an answer below. Unfortunately, this has not solved my issue and the error message stays the same.

var mapComponent = Ext.define('SIG.view.main.Map', {
  // extend: "Ext.panel.Panel",
  extend: 'GeoExt.component.Map',
  xtype: 'mappanel',
  //region: 'center',
  requires: [
    'SIG.view.main.MapController', 'SIG.view.main.MapModel'
  ],

  controller: 'main-map',
  viewModel: {
    type: 'main-map'
  },
  // html: "Hello, World!!"
  map: my_map,

  // Register the on render here
  listeners: {
    render: function(){
      console.log("Map rendered"); //this prints
      Ext.create('GeoExt.data.MapfishPrintProvider', {
         url: 'https://apps.terrestris.de/print-servlet-3.1.2/' +
               'print/geoext/capabilities.json',
         listeners: {
           ready: onPrintProviderReady
         }
      });
    }
  }
});

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.