If you ever needed to use WebGL to render large HTML5 canvases, you might have noticed, that your image might be pixelated. It definitely happened to me, but thanks to feedback from Globus users, I figured out what the problem was.
It turns out that all browsers (at least at the moment of writing this article), have some form of limiting the size of the render. So they will reduce the size of the render buffer, and then scale the image to the size of the canvas. To make things better JavaScript might not tell you about it either. Everything appears to be fine, but your renders become ugly and pixelated. Worst thing is – you don’t know why.
While working on Globus, I wrote a little piece of JavaScript, that is able to tell you what is the maximum canvas size you can use. determineMaxCanvasSize()is the function you should be most interested in:
You can also view it in a fiddle: https://jsfiddle.net/1sh47wfk/1/
]]>I’ve been bragging about Globus recently – allegedly, you can make your own projections for it. It’s true, let me show you how to do it!
First here are links to some projections, which are available from the drop down list. We’ll use them for reference:
Please open equirectangular. First thing you’ll see is:
...SECTION TOCALCULATIONS:
Projection files are divided into sections. There are 7 sections:
Now let me tell you what exactly does each section, and how to write them. Let’s project from Equirectangular to Sinusoidal. But beware, I will not go in order!
3. TOFORM:
When we project to Sinusoidal, we want the user to pick the meridian that will be in center. What is the way to collect data from a user on a website? HTML forms. We need to write html code that will appear below Project to:, and will let the user pick the central meridian. Here’s how we did it:
1

Pick central meridian (from 180 to 180): <input max=“180” min=“180” name=“meridian” type=“number” />

Fairly simple form – just one input. You can write any html code you wish, you may even add <script> … </script> sections. Then, once the user presses Project, Globus Map Projector will extract the data from the form, and enable you to use it when calculating the projection. However, only some input types will be extracted by Globus:
As you have probably noticed each of those inputs was also given a name. It is crucial – you will use this name as a name of a variable when you calculate the projection. You will learn how to use those variables in the next section.
1. TOCALCULATIONS:
This is the very heart of our projection. Here we define how to project TO our sinusoidal projection. Such calculations are done on shaders in Globus Map Projector, so this section must be written in GLSL (GL Shader Language). Basically, it is very similar to C, however it has some important differences, which I will list at the end of this section.
TOCALCULATIONS’s job is simple. As input you get coordinates of a point in your TO projection. Then you need to return where this point is located in equirectangular projection. Let me show this in a picture:
We get a point on output image, for example point (x, y):
We need to find where this point is in equirectangular projection. When TOCALCULATIONS’s section finishes, color from pixel (x2, y2) will be set in our output image at pixel (x, y).
Let’s go to practice. TOCALCULATIONS section is body (code between the curly brackets) of a function of this type:
1
2 3 4 5 6 7 8 
vec2 projectPointFromSinusoidalToEquirectangular(vec2 coords)
{ // do your projection code float x = coords.x; // coords.x is X coordinate of projected point (check the black and white image few lines earlier) float y = coords.y; // coords.y is Y coordinate of projected point return vec2(x, y); // we return a vector which contains point (x2, y2) from above image. 
As you have probably noticed, this code does nothing – it projects a point to itself. To make a real projection we need to learn three more things.
1. All coordinates (input and returned) are floating point numbers between 0 and 1. x = 0 is the left side, x = 1 is the right side, y = 0 is the top, and y = 1 is the bottom.
2. If we want a given point to be blank (if the point doesn’t belong to the projection), we return vector with at least one negative coordinate. For example: return vec2(1.0, 1.0); // this point is blank
3. We get access to our variables from TOFORM section. We will call the variables that the user created uniforms (because they behave just like GLSL’s uniforms), and they are constants defined at the top of our function. Check this list:
1

const float someFloat = …..; // number provided by the user

1

const vec4 someColor = …..; // color provided by the user

1
2 
const float someFloat = …..; // selected number from range. For example if the user picked 12/250
// this will be: const float someFloat = 12.0; 
1

const bool someBool = …..; // true if the checkbox was selected, false if it wasn’t selected

1
2 3 4 5 
const int someInt = …..; // Globus will count all the radios with the same name and return index
// of the one that was selected. For example, if we have 5 radios with // name “radio123”, in 5 lines, and the radio in 3rd line was selected // we’ll get: <em>const int radio123 = 2;</em> and if the radio from 1st line was // selected we’ll get: <em>const int radio 123 = 0;</em> 
1

const int someInt = …..; // index of selected option (remember we count indexes starting from 0)

Knowing that we can now fully understand our sinusoidal projection. Analyze it yourself, it should be quite simple now!
Important things to know about GLSL in WebGL:
4. FROMFORM:
Very similar to TOFORM. However, in this form, the user writes with what parameters they created the FROM projection. In our case, if the FROM projection (equirectangular) was made using 80 as central meridian, the user writes 80 in pick central meridian. Data collected from user in this section will be sent to FROMCALCULATIONS.
2. FROMCALCULATIONS:
Opposite to TOCALCULATIONS – you need to calculate, where a point in equirectangular projection is on your projection. Let me illustrate that.
You have a point (x2, y2) on equirectangular projection:
And then, you want to find its position on your projection (or, in other words, project it to your projection):
FROMCALCULATIONS programming is identical to TOCALCULATIONS. You need to write body (code between the curly brackets) of a function of this type:
1
2 3 4 5 6 7 8 
vec2 projectPointFromEquirectangularToSinusoidal(vec2 coords)
{ // do your projection code float x = coords.x; // coords.x is x2 from pictures above float y = coords.y; // coords.y is y2 from pictures above return vec2(x, y); // we return a vector which contains point (x, y) from above image. 
You will have access to uniforms from FROMFORM just as in TOCALCULATIONS you get access to uniforms from TOFORM.
7. MERIDIANLENGTHCALCULATIONS:
This section’s job is to say how long (in pixels) a meridian is in our FROM projection. In our case, the input image is in equirectangular (we project from equirectangular), so meridans span from the very top of the image to the very bottom:
That’s why the length of meridian in pixels is input image height. However, we also know that in equirectangular projections height = width/2, so meridian = width/2. Of course we could use meridian = height just as well.
Ok, so now how to write it?
MERIDIANLENGTHCALCULATIONS section is javascript code. Imagine it is a function (it actualy is a function, run through eval()) which gets width and height of the input image, and uniforms (from Project from form). Your job is to write body of a function (code between curly brackets) that will return the length of meridian.
1
2 3 4 5 
function calculateMeridianLength(width, height, uniforms)
{ // do some code return …..; // return length of meridian } 
Few lines ago I mentioned that MERIDIANLENGTHCALCULATIONS also receives the uniforms from FROMFORM. Why? Check sinusoidal intercepted projection – it turns out, that we can make the gores either parallel or concentric. In each of those cases projection size is different: in parallels width/height = 2, but in concentric width = height. This means that we have to distinguish type of gores when we calculate length of meridian. That’s when uniforms come in handy. Uniforms is an array of uniforms, and each uniform is a structure containing 3 fields:
If we want to get a uniform of a given name from this array, we can use getUniformFromArray(array, name) method. It’s quite easy, let’s check it on sinusoidal intercepted projection:
1
2 3 4 5 6 7 8 9 
switch (getUniformFromArray(uniforms, ‘interceptionType’).value)
{ case 0: return width/2; break; case 1: return width; break; } 
Let’s concentrate on:
getUniformFromArray(uniforms, 'interceptionType').value
As you can see, we simply ask to extract uniform of name interceptionType, and once we get it, we extract its value. Quite simple, isn’t it?
5, 6. OUTPUTWIDTHCALCULATIONS and OUTPUTHEIGHTCALCULATIONS:
This javascript sections are used to set the size of output image. The input is length of meridian in pixels and uniforms from TOFORM; as the output, you need to return either width or height.
I hope that now it’s clear how to make your own projections. I encourage you to write some, and you can send them to winski@winski.net, so I can check them, and then add them to the website. Don’t forget to write your name (in the code or in the forms) if you want attribution, and give permission to use it or add license notice to the code.
Write in comments, if you have questions!
Thank you and cheers
]]>I’ve just publicized the map projector I am working for. You can check it here. It is written in javascript + html + WebGL, so you don’t have to download anything – it’s fully online! As for now it only supports 3 projections (of which one 1 is equirectangular), but it is quite easy to add new, and I will add them shortly (and you can write your own projections too!). Check it for more info!
Cheers
]]>I’m starting to write a new map projector, which will work in javascript + WebGL. Basically, it means two things:
I made a quick demo, which you can access here (demo no longer available, check development version). It is not a final version, and I use it only to show you what the new projector will be able to do. Moreover, the code isn’t of production quality (it lacks comments, etc.). I’ll tell you when I make a fully functional version
Cheers
]]>