Graphics.BuildingAMapplugin - lordmundi/wikidoctest GitHub Wiki
Building a Mapplugin
« Understanding package files | EDGE User’s Guide | Using IDF Handcontroller camera »
On this page… (hide)
- 1. Simple directions
- 2. What’s really going on
- 2.1 What is a mapplugin?
- 2.2 Baby steps
- 2.3 Returning Elevation Data
- 2.4 Adding a mapplugin to a config file
- 2.5 Bumpmaps versus Elevation Only
- 2.6 Providing a Bumpmap
- 2.7 Providing Real Elevation and Bump Map Data
- 2.8 Providing Imagery
- 2.9 Caching and Map files
- 2.10 Planet Shaders
- 2.11 Using GDAL
- 2.12 Bumpmap Funnies
- 2.13 Important files
- 2.14 You’re not alone
NOTE: Mapplugin development is only supported on linux at this time (although you may be able to make it work on Mac). The cache files that are generated, however, can be distributed and used everywhere without others needing the mapplugin at all.
1. Simple directions
- There are no simple directions.
2. What's really going on
Building mapplugins is not a common enough task to devote large amounts of infrastructure to in order to make it simpler. But, if you find yourself needing to load some planetary elevation or imagery data and it isn't data that the EDGE team can load up for you, then read on.
2.1 What is a mapplugin?
Mapplugins are essentially a plugin to a plugin. More specifically, they are plugins to the dsp_mapservice plugin. When loading imagery or elevation data, the dsp_mapservice plugin can call a number of mapplugins to provide the imagery or elevation for a particular layer on a planet. This provides an abstraction so that, in theory, users could have mapplugins that return data from a local file, or ones that query a remote webservice, or even a formula that calculates the data. In essence, the mapplugin is queried to fill in a "patch" of data between a certain min/max latitude and min/max longitude for the planet layer. All the mapplugin has to do is fill in the array passed into it with its data. The mapservice doesn't care where the data comes from.
2.2 Baby steps
The easiest way to dive into learning about mapplugins is to look at an example. EDGE provides some examples and a build environment in "src.dist/mapplugin".
The best one to look at first might be "moon_elev_all_zero.c". This mapplugin basically just returns zero for the elevation of everything on the moon. To see how it does that, let's first skip down to the very bottom of the file. There, you will notice the following lines:
DSP_InitializeMapServer()
{
DSF_InstallMapService( "moon_elv", DSD_MAP_SERVICE_RESOLVE_ELEVATION, ResolveElevationData, NULL );
DSF_InstallMapService( "moon_bmp", DSD_MAP_SERVICE_RESOLVE_IMAGE, ResolveBmp, NULL );
return 0;
}
The function "DSP_InitializeMapServer()" is the only function in this file that is automatically called when the plugin is loaded, so it is essentially the main function of the program. Inside of it you can see two lines to register map service callbacks. One installs a callback to the function "ResolveElevationData" and tells the mapservice plugin that is is to be associated with the mapserver id called "moon_elv" and will return data of the type "DSD_MAP_SERVICE_RESOLVE_ELEVATION". Data of this type is just essentially floating point numbers.
The second line also registers a callback to "ResolveBmp", but it is for the mapserver id called "moon_bmp" and will returning image data rather than elevation. This means it will be filling in RGB triplets instead of single values like elevation will. We'll come back to this one later.
If you needed to do initialization in your mapplugin, (for example, opening the source data file or attempting a connection to a remote service), this is the function where you would do it.
2.3 Returning Elevation Data
Let's just focus on the elevation callback. What will happen is as portions of the moon come into view that require new data, the registered functions will be called for them to fill in data. While we are far away from the moon, the patches we are asked to fill in for elevation will be quite large. As we get closer and closer and the plugin decides it needs higher resolution data, it will call our mapplugin to fill in higher and higher resolution data. In these cases, our ResolveElevationData function will automatically get invoked for each patch. Let's take a look at that function.
static int
ResolveElevationData( DSS_MAP_INFO *pmap_info, float *elvdata, double priority, void *nullptr )
{
int i, j;
double lat, lon;
double dlat, dlon;
float *psval;
int got_data = 0;
float val;
dlat = (pmap_info->max_lat - pmap_info->min_lat) / (pmap_info->h - 1);
dlon = (pmap_info->max_log - pmap_info->min_log) / (pmap_info->w - 1);
psval = elvdata;
for( lat = pmap_info->max_lat, i = 0; i < pmap_info->h; i++, lat -= dlat )
{
for( lon = pmap_info->min_log, j = 0; j < pmap_info->w; j++, lon += dlon, psval += 1 )
{
*psval = 0.0;
got_data = 1;
}
}
if( got_data )
return DSD_DATA_COMPLETE;
else
return DSD_DATA_UNAVAILABLE;
}
As you can see, the input structure that contains the information about the patch we need to fill in, is the pmap_info structure which is of type DSS_MAP_INFO. On the first couple of lines you can see we are extracting out the min/max latitude and longitude (lovingly shortened to "log" in some places and "lon" in others). These min/max values and the width and heigh of the patch are used to figure out what the step size is for latitude and longitude and stored in dlat and dlon respectively. This means that each neighboring data point in the array is represented on the physical planet as being "dlat" away from its nearest neighbors in the latitude direction.
Once we have dlat and dlon, we can walk through the array in a for loop and fill in the data for each point in the array. Inside this for loop is where we would normally put in a call for each latitude and longitude lookup to get the elevation data at that point to fill in the array with. Since this mapplugin is just filling in zeroes, we just stick the zero in directly, but the structure is left there to make it easier to adapt for real data.
This for loop will iterate over the entire patch providing zeroes for the elevation data. This means that we are saying there is 0.0 inches of deviation from the reference ellipsoid for this planet defined in the config files. I only say inches because that is almost always the units folks are using for their planet definitions, but I suppose it could be whatever you definite it to be. All units of models and planets that are delivered with EDGE are in inches.
That's pretty much all there is to it for returning elevation data - fill in the array with floating point values representing the elevation at each lat/lon in the array and you're good to go.
Let's imagine we have a mapplugin that only returns elevation data as we have discussed so far. How do we use it?
2.4 Adding a mapplugin to a config file
If you are adjusting the configuration for an existing planet in EDGE (such as the Earth, Moon, or Mars), you will want to copy the provided config file from $DOUG_HOME/configs to your $USERDATA/configs. For example, to edit the moon, you would copy "configs/planet2_moon.cfg" into "$USERDATA/configs". To tell EDGE to use your config file instead of the default one, in your DEFINES block of your user.cfg, you will override the variable pointing to the config file for that planet. For the moon, you would override "PLANET2_MOON_CFG" to point to "${USERDATA}/configs/planet2_moon.cfg". You can see the definition of these variables in the edge_settings.cfg file.
On the other hand, if you are adding in a new planetary object, you will not need to redefine a variable. Just make your own config file in "$USERDATA/configs" using one of the delivered ones as a template, and then source that file in the POSTLOAD block of your $USERDATA/user.cfg as you would any other config file.
Inside your config file for that planet, you will add a DSP_MAPSERVICE block at the top. You can either copy it straight from "configs/planet2_generic.cfg", or you can use the following template:
DSP_MAPSERVICE
{
command_line
{
-mapplugin_dir "${DOUG_HOME}/mapplugin_${VR_HOST_CPU}"
-mapfile_dir "${PLANET2_GISDATA_DIR}"
}
plugins
{
# earthdefault mapearth
# moondefult mapmoon
# mars_merged mars_viking_merged_base
moon_zero moon_elev_all_zero
}
}
In that plugins block, you can see the listing for the mapplugins (with the existing ones commented out. The first part of the name is just an alias mapplugin and can be anything. The second part is the name of the shared library without the file extension. This is the file that will get loaded and called. In the template above I added in a mapplugin for the moon_elev_all_zero.
2.5 Bumpmaps versus Elevation Only
So we've got a mapplugin now loading that provides elevation data. What will that look like? Well, the first thing to understand is that elevation data must be drawn by adding more triangles to the mesh representing the planet. These triangles can be expensive. For example, if we have a view from far away that shows the entire planet, loading all the triangles for elevation data for a tiny patch on the surface doesn't make much sense. So, these triangles have to be paged in depending on their size in the view.
The second thing to understand about these triangles is that you only really see them due to shading. If you take a cube and draw it on the screen with all faces the exact same color, and remove the edge lines, you will be unable to see where one face stops and another starts. Unless of course, it is shaded with some sort of light model. This is normally done using the normal vector of the surface. A great overview of how this is done can be found here: http://en.wikipedia.org/wiki/Shading
So, knowing that the triangles cannot be drawn at pixel level resolution (for performance reasons), and that the shading is really what causes those triangles to show up, your elevation data will look somewhat faceted and chunky because the normal vector over an entire triangle is the same. You will only be able to draw so many triangles and shade them on a per triangle basis.
However, there is a solution to this. Since the normal vector is really what is needed to do shading at a resolution higher than the geometry, we can use the power of shader programs that run on the GPU itself and shade each pixel according to its normal vector. This allows us to draw triangles at a reasonable resolution, but give the appearance of much higher resolution geometry by shading it on the GPU at the pixel level. The map that stores this normal vector for each position on the planet is what we refer to as the bump map. You can read more about bump mapping here: http://en.wikipedia.org/wiki/Bump_mapping
The picture below demonstrates the difference between elevation only shading and shading with an addition bumpmap layer. Remember that both of these versions are showing the same geometry in terms of the triangle mesh - it is only that the bump mapped version is adding the shading at a much higher resolution.
[![Shading method comparison][22]][22]
Geometry based vs. image based shading
2.6 Providing a Bumpmap
Ok, so you clearly want a bumpmap for your elevation data, right? The good news is that you really don't need any additional data beyond the source elevation data and the way to query it for a particular lat/lon. And the additional code you need for the bumpmap is pretty much all provided in the examples. Let's see how it works.
If we go back to our example for moon_elev_all_zero.c file, you will now remember that we have that other callback to provide bumpmap data registered at the bottom. That function is similar to the one that fills in the elevation data patches, but it returns data of type _IMAGE instead of _ELEVATION. Why is that? Well, because the normal vector deviation is stored in an RGB triplet. The red value in the triplet stores the magnitude of the normal vector in the longitude direction, and the green value stores the magnitude of the normal in the latitude direction. The blue value is basically unused, but is sometimes used to mark valid and invalid data. If you are writing your own shader you can use the blue channel for whatever you like. These magnitudes are scaled to be between 0 and 255.
By itself, providing the bump-map data doesn't do anything. It is the shader which changes the color of each pixel which is where the bumpmap is used. The good news is that if you don't provide a customized shader to use the bumpmap data, a default one is provided which does the type of image based shading shown in the picture above.
Here is another image showing the difference bump mapped shading can make when the global elevation data for the moon is added in. Notice near the terminator the grazing angle shading really makes it look like there is some elevation there when in fact no additional triangles are being drawn for that elevation:
Grazing angle shading with the global elevation data provides a much more realistic look. Click for larger comparison image.
2.7 Providing Real Elevation and Bump Map Data
So, returning zeroes is easy. But it is obvious that for each of those lat/long locations inside those for loops, we will need to call a function that can return us the elevation at that specific lat/long. And that function will be where the real work is. For bumpmaps, we effectively do the exact same thing, except we sample a little to the left and right to calculate the slope in the longitude direction, and then a little above and below to get the slope in the latitude direction. So in that case we still need a function that can return us the elevation for any given lat/long.
Now, the chances of requesting data for an exact lat/long that is specified in your source data is almost zero. What will happen in almost every case is that you will need to find the closest points in your source data to the lat/long being requested and then do some sort of interpolation to return the best value you can. In our examples, we use a bilinear interpolation by getting the closest 4 pixels around the requested point and then taking a weighted average. Data points closer to the requested point carry more weight than those further away. We have some great ASCII art showing this in some of our GetDataSample functions:
/* This function returns the data for a particular lat/long. Since we are dealing with floating point
* data, the chances of hitting the exact values for which we have a datapoint are nill, so we take a
* weighted average of the 4 samples around the requested point. The weighted averages look like this:
*
* s1 s2
* #----------------------------#
* | | |
* | W4 | W3 |
* | | |
* |--------------------X-------|
* | | |
* | W2 | W1 |
* #----------------------------#
* s3 s4
*
* Average = s1*W1 + s2*W2 + s3*W3 + s4*W4
*
* This geometrically gives higher weight to the values closest to the requested location.
*
* (The total area you would normally divide by is 1 here, so you don't see it)
*/
Take a look in "src.dist/mapplugin/gdal_mapplugins/apollo15_img_test.c" and you will see one example of this GetDataSample function. For the sake of brevity, I will not include the entire function here.
2.8 Providing Imagery
Ok, so now you have a bumpy planet. But what if you don't have elevation data and only have imagery. Or what if you have both? Well, the same shader that is specifying the color of those pixels but looking at that bumpmap (or not), can also read an imagery map to get a base color. So, just like your mapplugin can provide RGB triplets to specify the bumpmap values of the normals, you can also provide RGB triplets to use as the imagery RGB values. Similarly, you can specify "aux" maps which are additional RGB maps that you can use to do other things in your shader. For example, we used an aux map and a customer shader once to highlight areas on the surface that were beyond a certain slope and therefore unsafe to land on.
Let's look at an example. If we look at "src.dist/mapplugin/gdal_mapplugins/apollo15_img_test.c", you will see it registers to provide imagery using the ResolveImg function. Let's take a look at that function.
NOTE: it is important to point out that for the moon, it is common to provide imagery as a greyscale single number rather than an RGB triplet. That is the case here - the source file contains a greyscale value for the color.
static int
ResolveImg( DSS_MAP_INFO *pmap_info, unsigned char *imgdata, double priority, LDEM_INFO *pinfo )
{
int i, j;
double lat, lon;
double dlat, dlon;
float e1;
unsigned char *pcol;
int got_data = 0;
/* GET WHAT DATA THERE IS AND WE WILL UPDATE ONLY THE SAMPLES THAT WE HAVE DATA FOR */
DSF_GetMapData();
dlat = (pmap_info->max_lat - pmap_info->min_lat) / (pmap_info->h - 1);
dlon = (pmap_info->max_log - pmap_info->min_log) / (pmap_info->w - 1);
for( pcol = imgdata, lat = pmap_info->max_lat, i = 0; i < pmap_info->h; i++, lat -= dlat )
{
for( lon = pmap_info->min_log, j = 0; j < pmap_info->w; j++, lon += dlon, pcol += 3 )
{
e1 = GetDataSample( lat, lon, 1.0, pinfo );
if(e1 != pinfo->nodata)
{
got_data = 1;
/* Clamp values */
if ( (int)e1 > MAX_IMG_GREY_CLAMP ) {
pcol[0] = pcol[1] = pcol[2] = MAX_IMG_GREY_CLAMP;
} else if ( (int)e1 < MIN_IMG_GREY_CLAMP ) {
pcol[0] = pcol[1] = pcol[2] = MIN_IMG_GREY_CLAMP;
} else {
pcol[0] = pcol[1] = pcol[2] = (int)e1;
}
}
}
}
if( got_data )
/* THIS WILL INFORM THE MAPPING SERVICE THAT WE HAVE PROVIDED ALL THE DATA NEEDED FOR THIS TILE */
return DSD_DATA_COMPLETE;
else
/* THIS WILL INFORM THE MAPPING SERVICE THAT WE HAVE NO DATA TO PROVIDE FOR THIS TILE */
return DSD_DATA_UNAVAILABLE;
}
The ResolveImg function has a similar for loop as the previous ones we discussed. It makes a call to "GetDataSample" to get the image value for a particular point, and the GetDataSample does a weighted average to give us the best data it can for that location. We have some extra logic in our function to clamp the values between a min and max, and similarly you can experiment any extra logic you see fit to filter out certain data, mask elements, brighten up imagery, etc.
2.9 Caching and Map files
As you are working with mapplugins, you will undoubtedly find yourself adding print statements to figure out when your functions are being called to load in new data. And you may be surprised to see that occasionally things aren't being called when you expect them to be. This is because there is a caching mechanism at work that stores off the data from each mapplugin as it provides data for each patch at different resolutions. This is also why once you have loaded up sufficient data from a mapplugin, you can disable the mapplugin directly and just run using the map file caches (which are able to load up the data for display extremely quickly).
These map files are traditionally what is stored in the "gisdata" directory. Some map files are delivered with EDGE. You may also create your own with your own mapplugins.
There are two different types of map files: ones that end in ".map", and others that end in ".ovl". The ".ovl" files are considered overlay map files. Overlay map files are referenced to overwrite data from a base ".map" file for any areas that they cover. So, for example, the .map file for the moon imagery may have complete coverage for the entire moon. If I want to deliver a file to someone to add in high resolution imagery for a small landing area on the moon, I don't want to have to recreate an entire new moon map. I would just provide the .ovl map that just overwrites the imagery provides in the base map.
One common working scenario is to make the base maps read-only, that way EDGE doesn't try to add mapplugin provided data into an existing map, and just leave the .ovl file as read-write. This is the mechanism EDGE uses to know when to create a .ovl file or not. Traditionally the map files provided with EDGE are set as read-only so adding in your own mapplugin for a map server ID that already exists should make a .ovl file for you.
2.10 Planet Shaders
If you take a look at "configs/planet2_earth.cfg" you can see some of neat ways you can customize shaders for planet layers. First off, you will notice the earth is different from the moon in that it has two different layers: a ground layer and a cloud layer. That layer name is used as the first argument in the settings definitions you see.
layer_node(ground,EARTH_GROUND)
#expel_node(ground,JIMBO,300.0)
specular(ground,0.8,0.8,0.8)
diffuse(ground,1.0,1.0,1.0)
shininess(ground,30.0)
img_map(ground,earth)
aux1_map(ground,earth_cloud)
aux2_map(ground,earth_lights)
shaders(ground,default, "${SHADER_DIR}/earth_ground_city_cloud.fs")
layer(cloud,${PLANET2_EARTH_CLOUD_ELEVATION},${PLANET2_EARTH_CLOUD_MIN_SIZE})
layer_node(cloud,CLOUDS)
blend(cloud,GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
diffuse(cloud,1.0,1.0,1.0)
diffuse_exp( cloud, 0.25, 1.0 )
specular(cloud,0.0,0.0,0.0)
shininess(cloud,0.0)
img_map(cloud,earth_cloud)
# bmp_map(cloud,earth_cloud_bmp)
# shaders(cloud,default,"${SHADER_DIR}/earth_cloud_bmp.fs")
shaders(cloud,default,"${SHADER_DIR}/earth_cloud.fs")
For the cloud layer (the one at the bottom), you can see that it has an image map to provide actual clouds you see. At one time we experimented with making the clouds have a bump map, but we took that out for performance reasons, which is why the next two lines are commented out. Finally, you can see that the cloud layer is using a customized shader called "earth_cloud.fs". You will notice that the img_map line for the clouds specifies that the map server id that provides the data is "earth_cloud".
Now, if you look up to the ground layer, you will notice it has an "aux1_map" and the map server id for it is "earth_cloud". This is because the shader for the land imagery draws shadows when there are clouds above it. To be able to know that, we reference that data in the aux1_map and have it available in the shader. This is possible without needing to know about the clouds in the mapplugins for the earth imagery because of the mapservice architecture which lets servers get reused. Similarly, a second auxillary map is used to light up the imagery if it is nighttime and the aux2_map signifies that there are city lights for an area. All of this can be seen in the fragment shader specified for the ground layer, earth_ground_city_cloud.fs.
The shaders themselves are written in openGL shading language. They can be a bit intimidating if you are not used to this language. You can find a reference for the language here: https://www.opengl.org/documentation/glsl/
I won't go through the shaders here, but I will mention one thing. At the top of the "earth_ground_city_cloud.fs" shader, you will notice some lines that start with "parameter". This is not openGL shading language. This is some DOUG specific code that gets stripped out and is used to link tcl commands that control the shader at runtime. This is SUPER cool. You can see that it is used to toggle cloud shadows on and off for the ground layer of the earth. It is also used to toggle on and off the water detection (which makes the ground much more specular and reflective of sunlight). If you look in earth_clouds.tcl, you can see that it is used to show lightning in the clouds (combined with the lightning_test.tcl script to calculate the random strikes and position storms). You can probably come up with many more ways to use this tcl linkage. A post describing how to use them is here: 00101: feature to allow external variable to be mapped to planet2 shaders
2.11 Using GDAL
So, you are reading all this, and you build a mapplugin. You work your way through figuring out how to read a file that is in PDS format and parse it. The file you are working with is in some strange coordinate system that you figure out how to work with. Then you figure out what sort of projection it is in and work through that. And then you debug, and after a while, you have a working mapplugin and some map files you can use.
But then, another project comes along and you need to do it again. But this time the file is in GeoTiff format. And a different coordinate system. And a polar stereographics projection (or some other thing you've never heard of). All that code has to be worked through again. Certainly there has to be a better way!
There is. It's the wonderfully amazing library called GDAL: the Geospatial Data Abstraction Library. You can find information about it here: http://www.gdal.org/
This library saved me many times, so I started writing some mapplugins that used it and distributing a linux version of the library with EDGE in case others would like to use it. This is why there is a separate directory called "gdal_mapplugins".
The idea behind GDAL is that it abstracts away all those items like file format, projections, etc. It provides an abstract data model you can query with the library taking care of the file specific I/O behind the scenes. The mapplugins in the gdal_mapplugins folder use this library. They don't use it very efficiently - the library could be used to fill in an entire patch at a time, but I haven't taken the time to figure out how to do that. So I just use it to extract out individual points at a specific lat/long as we discussed above. If you figure out how to sample out an area into the patch being requested all at one (which would speed it up significantly), be sure to pass that code back to us!
Here are what I did to configure and compile GDAL 2.1 using a 32bit CentOS 7 on a virtual machine. —Zuqunli May 18, 2016, at 6:08 PM
Download GDAL source code from http://www.gdal.org/
cd ${gdal_path}
./configure --with-libtiff=internal --with-geotiff=internal --without-sqlite3 --without-png --without-jpeg \
--without-grib --without-curl --without-mrf --without-expat --without-odcb --without-pgeo \
--without-pcre --without-xml2 --without-pg --without-pcidsk --without-qhull
make
sudo make install
Then I copy gdal include files (from /usr/local/include) and gdal library files (from /usr/local/lib) to my 64 bit CentOS 7 and put it under ${EDGE_HOME}/src.dist/mapplugin/gdal_mapplugins/ directory.
2.12 Bumpmap Funnies
I wanted to put a section in here to just point out that the mapservice code is not without its quirks. Previously I have found that a planet can have some funny things happen to the shading when a bump map is supplied for an area when the entire planet doesn't have valid values in the bump map. For example, if you start a new bump map for a planet which doesn't have one, and only provide data for a small patch, I've had issues with the shading looking extremely odd. This is actually where those "_all_zero" mapplugins came from. I have used them to provide a starting bump map for an entire planet with all zeroes, and then added on to it with another mapplugin that provides new bump map data for a specific area.
There are other funnies - sometimes it is easiest to just delete the map files you are working on and start fresh.
2.13 Important files
Here is a listing of some of the important files and what they do as a quick reference:
-
configs/planet2_earth.cfg The planet2 config file for the Earth, which includes the ground layer with cloud shadows and city lights, and also the cloud layer. Override the variable "PLANET2_EARTH_CFG" if you wish to provide your own customized moon definition.
-
configs/planet2_moon.cfg The planet2 config file for the moon. Override the variable "PLANET2_MOON_CFG" if you wish to provide your own customized moon definition.
-
configs/planet2_mars.cfg The planet2 config file for Mars. Override the variable "PLANET2_MARS_CFG" if you wish to provide your own customized Mars definition.
-
configs/planet2_generic.cfg The generic config file for planet2 that sets settings that aren't specific to any planet
-
shaders/earth_ground_city_cloud.fs The shader being used for the earth ground layer as of this writing.
-
shaders/earth_cloud.fs The shader being used for the earth cloud layer as of this writing.
-
mapplugin_Linux_FC3/ The directory where the compiled mapplugin shared objects are stored. Substitude _Linux_FC3 for different VR_HOST_CPU types (_Darwin for example)
-
src.dist/mapplugin/*.c Example mapplugin source files delivered with EDGE that do not use the GDAL library
-
src.dist/mapplugin/gdal_mapplugin/*.c Example mapplugin source files delivered with EDGE that use the GDAL library
-
src.dist/mapplugin/gdal_mapplugin/lib/libgdal.a A precompiled version of the GDAL library for 32 bit which can be used on linux machines to build GDAL mapplugins for EDGE
-
src.dist/shaders/*.fs Example planet2 shaders that show the built-in shading behavior if the internal shaders are used.
-
gisdata/*.map Map file caches from map servers. Overlay maps are stored as .ovl files if the .map file is read-only.
2.14 You're not alone
Feel free to ask for help if you are building mapplugins! We'd be glad to help if we can. See the Support page.