Openlayers Cannot Read Property 'w' of Null

… where we investigate projections and view our map from an Arctic perspective.

Projections: different ways of looking at the world

In the last section we met the need to convert from longitude/breadth coordinates to some kind of what I'll very loosely telephone call "map units". This was necessary because nosotros needed to translate from units that humans are about familiar with (degrees longitude/latitude) to units that make sense on a 2D map. This is an important point: a map is a 2D representation of a 3D object (the earth).

It turns out that there are many means of representing our 3D world on a 2D surface, each with diverse advantages and disadvantages. To create a 2D map of the world, it's necessary to interpret every 3D location on the surface of the earth to a given location on the map. This process is called projection; the different second representations of the globe are called projections, considering they are generated by projecting a signal on the globe to a point in some 2nd space.

I of the most popular projections is Mercator, in item because it makes navigation easier, merely also because the surface of the earth is mapped to a Cartesian filigree and thus lines of abiding breadth are parallel with the x-axis and lines of constant longitude are parallel with the y-axis. Some other way of putting this is that north is ever "up" and south is ever "down"; westward is ever "left" and east is ever "right". This projection also uses metres every bit its unit, so that one can more easily measure distances in familiar units (rather than in, say, degrees breadth or longitude). These features of the Mercator projection make navigation in a ring around the equator much simpler and it is fairly intuituive, notwithstanding information technology means that the map is very distorted close to the poles: distances of a few metres in reality are "stretched" to several kilometres.

And then if nosotros're interested in a map of, say, the Arctic, what exercise we do? We use a Stereographic Projection, in particular one of the many Polar Stereographic Projections. In these cases, we have a map where the distortion at the pole is minimised, with the disadvantage of large distortions at the equator (merely hey, nosotros're not interested in the equator, so we can live with the baloney). A commonly used project for the Arctic is NSIDC Sea Water ice Polar Stereographic North, which nosotros'll migrate to slowly using our test suite as a guide.

Changing our focus to the Arctic

Let's choose a location in the Arctic upon which we can centre our map. A skillful candidate is Longyearbyen which is the largest settlement in the Svalbard archipeligo, is located north of the Chill circumvolve and is home to the world'due south northern-most university. It'south located at 78.217 Due north, 15.633 E, so allow'southward update our examination for the map centre to await to be here rather than in Prague; our exam will now look like this:

                          draw              (              '              Basic map              '              ,              role              ()              {              it              (              '              should have a view centred on Longyearbyen              '              ,              function              ()              {              let              lon              ,              lat              ;              [              lon              ,              lat              ]              =              toLonLat              (              map              .              getView              ().              getCenter              ());              lon              .              should              .              be              .              closeTo              (              15.633              ,              ane              east              -              6              );              lat              .              should              .              be              .              closeTo              (              78.217              ,              i              e              -              half-dozen              );              });              });                      

Running the tests (make test) gives this output:

                          Basic map     1) should have a view centred on Longyearbyen     0 passing (5ms)   1 failing    i) Basic map        should accept a view centred on Longyearbyen:      AssertionError: expected fourteen.439999999999998 to be shut to xv.533 +/- 0.000001       at Context.<anonymous> (exam/map-test.js:x:23)       at processImmediate (internal/timers.js:456:21)       at process.topLevelDomainCallback (domain.js:137:xv)                      

We expect this failure, since we oasis't updated the production lawmaking to centre on Longyearbyen all the same.

Update the eye aspect of the view to match the coordinates for Longyearbyen in src/js/index.js and re-run the tests. You lot should see that the tests laissez passer again.

                          Bones map     ✓ should have a view centred on Longyearbyen     one passing (5ms)                      

At present run brand build to create the webpack parcel and reload the map in your web browser. Y'all should see something very similar to this:

Map centred on Longyearbyen

We're now much more than confident that our code is doing what we intend it to practise. This is a adept point to commit the changes to the repository:

            # mention what the changes were and why in the commit message $ git commit src/js/alphabetize.js examination/map-test.js                      

Refactor time!

Nosotros plan to have multiple layers in this awarding: one for the map and one for the image data. Calculation the side by side layer in the layers array is going to make the code difficult to understand, so let's extract the OpenStreetmap TileLayer into its own variable:

                          const              osmLayer              =              new              TileLayer              ({              source              :              new              OSM              ()              });              const              map              =              new              Map              ({              target              :              '              map              '              ,              layers              :              [              osmLayer              ],              view              :              new              View              ({              center              :              fromLonLat              ([              fifteen.633              ,              78.217              ]),              zoom              :              4              })              });                      

Re-running the tests shows that nosotros haven't broken anything by making this modify. Let's too extract the View into its own variable:

                          const              osmLayer              =              new              TileLayer              ({              source              :              new              OSM              ()              });              const              arcticView              =              new              View              ({              middle              :              fromLonLat              ([              xv.633              ,              78.217              ]),              zoom              :              4              ,              });              const              map              =              new              Map              ({              target              :              '              map              '              ,              layers              :              [              osmLayer              ],              view              :              arcticView              ,              });                      

Again, running the test suite shows that we haven't broken anything. Note that I've jumped the gun hither a bit past calling calling the View variable arcticView because we aren't all the same using an Arctic projection, but that'due south not such a bad transgression as we're heading in that direction anyway.

Now commit the changes:

            # mention extraction of view and layer into own variables $ git commit src/js/index.js                      

Hrm, I'chiliad non happy with the map's proper name. Do y'all think your users will want the map to exist called "My Map"? No. Let's make this more descriptive by calling it "Arctic Sea-Water ice Concentration". Also, notice that the text in the title bar is "OpenLayers case". Users will probably retrieve this is a bit dodgy, so it as well needs a better name; how virtually "Arctic Ice Data"? These changes require the <h1> and <title> elements (respectively) to be fix correctly. Opening src/index.html in an editor, we can set up the title easily:

                          <title>Arctic Ice Data</championship>                      

withal, we notice that the "My Map" header is actually an <h2> element even though there'south no preceding <h1> element. That was naughty of us! Thus we need to change the tag proper name and its contents from

                          <h2>My Map</h2>                      

to

                          <h1>Chill Sea-Water ice Concentration</h1>                      

Now build the app with make build and reload it in your browser. It should expect something similar this:

Map title and header now match app's purpose and content

That's much better! Commit the changes and so that they don't get away from u.s.:

            # mention update of app championship and principal header in commit bulletin $ git commit src/alphabetize.html                      

View from the tiptop of the world

To let OpenLayers know that nosotros want to use the NSIDC Sea Ice Polar Stereographic Due north project, we add together the projection option to the View constructor and pass in a Projection object or by a string specifying the projection's EPSG codeone. By default, OpenLayers uses Spherical Mercator, also known as Pseudo Mercator or Spider web Mercator, considering it is unremarkably used for web-based maps.

OpenLayers has several projections that information technology already knows about, unfortunately, epsg:3413 isn't 1 of them. Yous can test this by adding the projection option to the View constructor:

                          const              arcticView              =              new              View              ({              center              :              fromLonLat              ([              fifteen.633              ,              78.217              ]),              zoom              :              4              ,              projection              :              '              EPSG:3413              '              ,              });                      

The test suite should fail with an error message similar this:

            /path/to/arctic-sea-ice-map/node_modules/ol/View.js:1 TypeError: Cannot read belongings 'getExtent' of null     at createResolutionConstraint (/path/to/arctic-sea-ice-map/node_modules/ol/View.js:1509:33)     at View.require.View.applyOptions_ (/path/to/arctic-bounding main-ice-map/node_modules/ol/View.js:338:40)     at new View (/path/to/chill-sea-ice-map/node_modules/ol/View.js:326:fifteen)                      

The error Cannot read property 'getExtent' of zero means that the view can't get the extent of a nada projection and hence the test fails. If you build the project (make build) and reload the app in your browser, y'all'll see that the app stops working.

Remove this change with

            $ git checkout src/js/index.js                      

and then that we go dorsum to a clean state before going further.

The solution to the problem of the missing projection definition is to define the projection ourselves, and to do this we need to utilise the proj4js library. Install the library via npm:

Now we need to import the proj4 library into our app also equally the register function from the OpenLayers proj4 library, so that nosotros tin tell OpenLayers well-nigh whatsoever new projections we define. Add these lines to the list of import statements in src/js/index.js:

                          import              proj4              from              '              proj4              '              ;              import              {              annals              }              from              '              ol/proj/proj4              '              ;                      

At present we can define the 'EPSG:3413' projection using its PROJ4 definition (encounter, e.g. the proj4js definition for the projection on epsg.io):

                          proj4              .              defs              (              '              EPSG:3413              '              ,              '              +proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +chiliad=one +x_0=0 +y_0=0 +datum=WGS84 +units=g +no_defs              '              ,              );                      

(this code can come afterward the import statements). At present we simply need to let OpenLayers know about this new PROJ4 definition:

                          // ensure OL knows about the PROJ4 definitions              register              (              proj4              );                      

This time, if we add the line

                          projection              :              '              EPSG:3413              '              ,                      

to the View constructor, we'll find that the tests pass.

Withal, if we build the project via brand build and view the app in dist/alphabetize.html we'll find that the map is centred over Australia and Republic of indonesia and that these are shown upside downward. What's going on now? Remember how nosotros were using fromLonLat and toLonLat to convert longitudes and latitudes into map-based (projection-based) coordinates? Well, these functions presume the OpenLayers default project (EPSG:3857 a.k.a. Web Mercator) unless told otherwise. We thus need to explicitly specify the project when calling these functions in order to correctly translate longitude/breadth values into the appropriate projection coordinates.

Let'due south prepare the test first. Add the argument 'EPSG:3413' to the toLonLat() call within our test:

                          information technology              (              '              should have a view centred on Longyearbyen              '              ,              function              ()              {              let              lon              ,              lat              ;              [              lon              ,              lat              ]              =              toLonLat              (              map              .              getView              ().              getCenter              (),              '              EPSG:3413              '              );              lon              .              should              .              be              .              closeTo              (              15.633              ,              i              e              -              six              );              lat              .              should              .              be              .              closeTo              (              78.217              ,              1              eastward              -              half-dozen              );              });                      

Running the test suite should at present barf horribly (but so, nosotros expect this to happen, and so it's not a bad affair).

                          Basic map     1) should accept a view centred on Longyearbyen     0 passing (9ms)   ane failing    i) Basic map        should have a view centred on Longyearbyen:      AssertionError: expected 128.14963323608137 to be close to fifteen.633 +/- 0.000001                      

A notation for those who have been watching closely: we didn't have to ascertain 'EPSG:3413' within the test file considering it got added to the global namespace when nosotros imported the map variable from the main package (in index.js). This came every bit a bit of a surprise to me (peculiarly as someone used to other languages); I'd expected that I'd accept to ascertain the projection in the test file because I'one thousand not exporting it from the main package. Notwithstanding, information technology seems that JavaScript not only parses the code it imports, but executes it besides, and then at that place can be side furnishings from using an import statement. This is expert to know and adept to keep in listen when developing JavaScript lawmaking that this tin can happen.

Returning to the main code, we now just demand to add the projection to the fromLonLat() call:

                          centre              :              fromLonLat              ([              15.633              ,              78.217              ],              '              EPSG:3413              '              ),                      

The exam suite will now pass. Yay! Also, building the project and reloading the app in a browser will evidence that we're now viewing the Chill.

Arctic map centred on Longyearbyen

To bank check that we definitely are centred above Longyearbyen, zoom in using the + push button in the elevation left-mitt corner. You should meet something like this:

Arctic map zoomed

which shows Svalbard and if y'all zoom in even further, you will find that it is, indeed, centred over Longyearbyen.

That's a nice result, and then let's commit this country to the repository:

            # mention that we're using the new projection in the commit message and why $ git commit package-lock.json package.json src/ test/                      

Remove projection name duplication

Have you noticed that we've referenced the cord 'EPSG:3413' several times? Not only is it adequately difficult to type, it's non skillful programming manner to direct refer to a string in several places; we should exist using a variable and then that we can test aspects of the projection definition, but besides so that we can avert typos in the cord and hence avoid having incorrectly defined projections being used in functions and constructors.

Let's define the project as a variable. To exercise this, nosotros need to import the get function from the ol/proj library:

                          import              {              become              }              from              '              ol/proj              '              ;                      

and then, after the proj4 definitions have been registered, define the project as a variable:

                          const              epsg3413              =              become              (              '              EPSG:3413              '              );                      

Now we can replace the projection strings with this variable; the View constructor looks thus:

                          const              arcticView              =              new              View              ({              center              :              fromLonLat              ([              fifteen.633              ,              78.217              ],              epsg3413              ),              zoom              :              4              ,              projection              :              epsg3413              ,              });                      

Running the examination suite, you should run across the tests pass. However, if we make the same replacement in the test:

                          it              (              '              should have a view centred on Longyearbyen              '              ,              part              ()              {              permit              lon              ,              lat              ;              [              lon              ,              lat              ]              =              toLonLat              (              map              .              getView              ().              getCenter              (),              epsg3413              );              lon              .              should              .              be              .              closeTo              (              xv.633              ,              i              e              -              6              );              lat              .              should              .              be              .              closeTo              (              78.217              ,              1              e              -              6              );              });                      

the tests will barf:

                          Basic map     1) should have a view centred on Longyearbyen     0 passing (9ms)   one declining    1) Bones map        should have a view centred on Longyearbyen:      ReferenceError: epsg3413 is not defined       at Context.<anonymous> (test/map-test.js:12:65)       at process.topLevelDomainCallback (domain.js:120:23)                      

which is non a bad thing, because nosotros're no longer referencing (via a string) a projection which has been implicitly made available in the global application namespace. What nosotros can do now is extract the project definition into a module and nosotros can import only the things that nosotros need from it: in this case, the projection object.

Permit'due south accept a footstep back and undo the change nosotros made to the test file, so that we can get the tests passing once more:

            $ git checkout test/map-examination.js                      

Now the tests pass and nosotros have a solid base to work from.

Our programme now is to extract the projection-related code into its ain module. Nosotros'll start by creating a test file for the projections module we desire to build. Open up the new file test/projections-exam.js in your favourite editor, import the chai library and add together the test suite clarification:

                          import              '              chai/register-should              '              ;              describe              (              '              EPSG:3413              '              ,              role              ()              {              });                      

1 of the simplest tests we can write would be to check if the projection's EPSG code matches that which we look: 'EPSG:3413'. This volition bootstrap the test suite for the projections module. Add together the post-obit test to the depict block:

                          it              (              '              should utilise EPSG:3413 as its projection code              '              ,              part              ()              {              epsg3413              .              getCode              ().              should              .              equal              (              '              EPSG:3413              '              );              });                      

The tests now dice with this error message:

                          Bones map     ✓ should have a view centred on Longyearbyen    EPSG:3413     ane) should use EPSG:3413 equally its projection code     1 passing (16ms)   1 failing    one) EPSG:3413        should use EPSG:3413 as its projection lawmaking:      ReferenceError: epsg3413 is not defined       at Context.<bearding> (test/projections-exam.js:5:5)       at process.topLevelDomainCallback (domain.js:120:23)                      

The important thing to note is that the variable epsg3413 hasn't been defined. That'south something nosotros should import from the projections module nosotros want to build, then let's import that into the test file:

                          import              epsg3413              from              '              ../src/js/projections.js              '              ;                      

Of course the examination suite dies horribly:

            /path/to/arctic-sea-ice-map/examination/projections-test.js:i Error: Cannot find module '../src/js/projections.js' Require stack: - /path/to/arctic-sea-ice-map/test/projections-test.js                      

because it can't notice the file we reference, so let's create that. Open the file src/js/projections.js in your editor and add the post-obit code:

                          consign              {              epsg3413              as              default              };                      

The tests withal fail (we didn't expect otherwise) but they get a scrap further (which we did expect):

            file:///path/to/arctic-sea-ice-map/src/js/projections.js:1 export { epsg3413 as default };          ^^^^^^^^  SyntaxError: Export 'epsg3413' is not defined in module                      

Now move the lines

                          import              proj4              from              '              proj4              '              ;              import              {              annals              }              from              '              ol/proj/proj4              '              ;              import              {              go              }              from              '              ol/proj              '              ;              proj4              .              defs              (              '              EPSG:3413              '              ,              '              +proj=stere +lat_0=xc +lat_ts=seventy +lon_0=-45 +k=one +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs              '              ,              );              // ensure OL knows nigh the PROJ4 definitions              annals              (              proj4              );                      

from src/js/index.js into src/js/projections.js (make sure to put this lawmaking above the export statement that is already in the file). The tests will still die; this fourth dimension because index.js doesn't know anything most the variable epsg3413.

            /path/to/arctic-sea-ice-map/src/js/index.js:1 ReferenceError: epsg3413 is non divers                      

Let's import it to get rid of the fault. Add together the line

                          import              epsg3413              from              '              ./projections.js              '              ;                      

to the end of the listing of imports in src/js/index.js. Now the tests pass!

                          Basic map     ✓ should take a view centred on Longyearbyen    EPSG:3413     ✓ should use EPSG:3413 as its projection code     two passing (8ms)                      

Now nosotros tin can apply our new module in the map tests. Add together the following line later on map has been imported into test/map-test.js:

                          import              epsg3413              from              '              ../src/js/projections.js              '              ;                      

and replace 'EPSG:3413' with the variable epsg3413. The tests still pass! Bully!

This is a proficient place to commit our progress to the repository:

            $ git add src/js/projections.js test/projections-test.js # mention extraction of projection into module and reduction of lawmaking # duplication in the commit message $ git commit src/ test/                      

Focussing more on the Arctic

Did you notice that the zoomed-out view of the Chill (mentioned above) showed quite a lot of the world? It even showed parts of Europe, Africa, the Middle Eastward and North America which won't ever accept sea-ice. Let's be more focussed on the Chill and limit the map view to a more specific area. Since this is an aspect of the projection object we'll demand to extend the tests for the projections module. Open test/projections-exam.js and add together the following test to the main describe block:

                          information technology              (              '              should use the NSIDC north pole max body of water ice extent              '              ,              function              ()              {              const              expectedExtent              =              [              -              5984962.406015              ,              -              6015037.593985              ,              6015037.593985              ,              5984962.406015              ];              epsg3413              .              getWorldExtent              ().              should              .              be              .              deep              .              equal              (              expectedExtent              );              epsg3413              .              getExtent              ().              should              .              exist              .              deep              .              equal              (              expectedExtent              );              });                      

where the extent is divers past the extremal lower-left and upper-right coordinates in the NSIDC Body of water Ice Polar Stereographic North projection.

Equally expected, the tests fail because we haven't all the same divers the extent for the project:

                          Basic map     ✓ should take a view centred on Longyearbyen    EPSG:3413     ✓ should use EPSG:3413 as its projection code     1) should utilize the NSIDC north pole extent     ii passing (11ms)   1 declining    1) EPSG:3413        should employ the NSIDC north pole extent:      TypeError: Cannot read belongings 'should' of null                      

The error message Cannot read property 'should' of nada tells us that the output of getWorldExtent() in the test returned null. If we now set the map and world extents in the projections module:

                          const              fullMapExtent              =              [              -              5984962.406015              ,              -              6015037.593985              ,              6015037.593985              ,              5984962.406015              ];              const              epsg3413              =              become              (              '              EPSG:3413              '              );              epsg3413              .              setWorldExtent              (              fullMapExtent              );              epsg3413              .              setExtent              (              fullMapExtent              );                      

we find that the tests laissez passer. Cool! To restrict the map view to the newly-divers extent, we laissez passer the extent parameter to the View constructor.

                          extent              :              epsg3413              .              getExtent              (),                      

The extent that OpenLayers uses for the view depends upon the zoom level and the resolution at which the map should be displayed, hence information technology's not sensible to test for the view's extent because this number isn't a constant. Withal, to see that the map behaves as we intend, build the projection (make build), load the app in a browser and zoom out to the minimum zoom level. Panning effectually you can see that the maximum extent for the map view is now much smaller than it was before.

This is a good place to save the state of the project to the repository:

            # mention that we're setting the projection and view extents explicitly (and # why) in the commit bulletin $ git commit src/ examination/                      

Condign more specific to Svalbard

Let'southward make the map more than specific to Svalbard by increasing the zoom level and rotating the map so that Svalbard has information technology's typical "inverted triangle" shape which we are most used to when viewing Svalbard on e.g. a Mercator projection. We thus want to increase the zoom level to six and rotate the map past 45 degrees. We tin put these requirements into tests and thus ensure that they are met.

Start, let's set the zoom level. Add this examination to the map-examination.js file:

                          it              (              '              should have a default zoom of 6              '              ,              function              ()              {              const              zoomLevel              =              map              .              getView              ().              getZoom              ();              zoomLevel              .              should              .              equal              (              6              );              });                      

The tests will fail considering we currently have a zoom level of 4. Set this value to 6 and run the tests once again:

                          zoom              :              half-dozen              ,                      

Yay, the tests are passing. Commit the change to the repo:

            # mention setting default zoom level in commit message $ git commit src/js/alphabetize.js test/map-test.js                      

Now allow'southward set the default rotation by adding this test to map-test.js:

                          information technology              (              '              should have a default rotation of 45 degrees              '              ,              function              ()              {              const              zoomLevel              =              map              .              getView              ().              getRotation              ();              zoomLevel              .              should              .              equal              (              45              *              Math              .              PI              /              180              );              });                      

Notation that angles have to exist in radians, hence the cistron of π/180.

Of course the tests fail:

                          ane) Basic map        should have a default rotation of 45 degrees:        AssertionError: expected 0 to equal 0.7853981633974483       + expected - actual        -0       +0.7853981633974483                      

Now fix the rotation property in the View constructor:

                          rotation              :              45              *              Math              .              PI              /              180              ,                      

Running the tests again shows that the examination suite passes.

                          Bones map     ✓ should have a view centred on Longyearbyen     ✓ should have a default zoom of 6     ✓ should have a default rotation of 45 degrees    EPSG:3413     ✓ should use EPSG:3413 as its projection code     ✓ should apply the NSIDC north pole max sea ice extent     five passing (10ms)                      

Building the app (make build) and opening it in a browser, y'all should see output similar to this image:

Svalbard-focussed map

Nice! This is what we want to see :smiley:

Commit this change every bit well:

            # mention that we're setting the default rotation in the commit bulletin $ git commit src/js/index.js test/map-test.js                      

Epitomize

We now have a ameliorate idea of what projections are and how we tin use them to control how a map is visualised. We've likewise been able to blast down more than details of the awarding and what nosotros wait it to expect like and acquit by building a minor tests then getting them to work. Therefore, slowly, bit by bit, nosotros're edifice an awarding on a foundation which is condign more firm with every test. This office also involved a lot of work; peradventure information technology's time you went to fetch a cup of tea or coffee before we dive into visualising Arctic sea-ice concentration data.

bennettthadfur.blogspot.com

Source: https://ptc-it.de/developing-openlayers-apps-in-es6-mocha-karma-webpack-projections/

0 Response to "Openlayers Cannot Read Property 'w' of Null"

إرسال تعليق

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel