How can I save a map in PostgreSQL? - ruby-on-rails

I'm currently working on an application for making geologic maps. I'm using Ruby on Rails for my back end, React for my front end, and Postgres for the database. I am using React Leaflet to display the maps (abstracts Leaflet.js to React components). I am getting the maps from OpenTopoMap. This application is non-commercial and I have checked their terms of use to make sure I am not in violation.
I want users to be able to view a map and be able to add points and draw polygons on it (I already know how to do this part). Once they are done drawing on the map I want them to be able to save the map with whatever additions they have made. They should then be able to reopen it later and modify it.
The only way I have found to save the map is as a .png, which means it can't be edited when it is reopened.
Saving only the points/polygons and re-rendering them when a user reopens the map is an acceptable solution as long as the correct area of the map is shown when it is reopened, but I am not sure how I would go about storing this data. A user can have many maps, so solutions that only work for a single instance of map will not work in this case.
I am aware that what I have described here just duplicates the functionality of many other mapping applications. Once this part is figured out I plan to add extensive functionality that is specific to making geological maps. I have not described that functionality here because it is not relevant to my current question.
How can I save a map to PostgreSQL in a format that will allow it to be edited when reopened?

Instead of reinventing the wheel with some JSON column monstrosity look into PostGIS. Its an extension for Postgres that adds geospatial data types like points, lines and polygons. It also lets you write geospatial database queries like for example testing if a point is in a polygon or proximity searches.
For rails the the activerecord-postgis-adapter gem adds these database types:
:geometry -- Any geometric type
:st_point -- Point data
:line_string -- LineString data
:st_polygon -- Polygon data
:geometry_collection -- Any collection type
:multi_point -- A collection of Points
:multi_line_string -- A collection of LineStrings
:multi_polygon -- A collection of Polygons
This provides the building blocks to create models with geospatial attributes. Its not a magic wand that will let you stuff a map with doodles into Postgres and get something editable out and will require a significant effort in both research and implementation.

I use leaflet and leaflet.draw together to save the geoJSON from drawing polygons or dropping markers and save it to a field called coords in the database. There is a good example of it here: https://github.com/jeremygradisher/mapping-leaflet
It boils down to using leaflet.draw to draw the coordinates and then saving that data where you need it. Here is leaflet.draw: http://leaflet.github.io/Leaflet.draw/docs/leaflet-draw-latest.html
Here I am using leaflet.draw to draw on the map and then I stringify the data and add it to a field to be saved in the database.
<script>
// Initialize the FeatureGroup to store editable layers
var drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
// Initialize and alter the draw control and pass it the FeatureGroup of editable layers
var drawControl = new L.Control.Draw({
draw: {
//These options make up the root object that is used when initializing the Leaflet.draw control.
position: 'topleft',
polygon: {
allowIntersection: false,
drawError: {color: '#00dd00', timeout: 1000
},
shapeOptions: {color: '#00dd00', weight: 1}, showArea: true},
polyline: false,
rectangle: {shapeOptions: {color: '#00dd00',weight: 1}},circle: false},
edit: {featureGroup: drawnItems}
});
map.addControl(drawControl);
//draw:created - Triggered when a new vector or marker has been created.
map.on('draw:created', function (e) {
var type = e.layerType,
layer = e.layer;
var shape = layer.toGeoJSON();
var shape_for_db = JSON.stringify(shape);
drawnItems.addLayer(layer);
//trying some things here
//enter in field to save to the database
document.getElementById('area_coords').value = shape_for_db;
});

As far as I understand you want to save complicated objects to the database. An user can draw lines on the map and can draw polygons and etc. Maybe using jsonb object for this problem can be your solution. Basically it's saving a JSON formatted field to database.
An example jsonb object can be like this.
{
"data": {
"type": "user_map",
"attributes": {
"user_id": "1",
"drawings": {
"points": [],
"lines": {},
"polygons": {}
}
}
}
}
You can both keep these information on user or on separate model with using has_many :maps on user model.
Links that can help you:
Example 1
Example 2

I would suggest not to think in terms of map (the rendering of spatial data) but in terms of data (a point/line/polygon with attributes). You would save each user data in tables, maybe have another map_config table that holds the map bounding box, name etc.
In you app, once you have authenticated the user, you give access to its map configurations (let him select the one to be shown) and associated data. You then display the map either using the raw vector data directly in Leaflet, Open Layers (or else), or you can pipe it through a geo server (Geoserver, Mapserver etc) for serving it as wms images, or wfs editable vector data. You zoom or restrict the displayed area to the one saved in the map configuration.

Related

Avoid calculating startIndex and endIndex when creating a document using Google Docs API

I have proven to myself that I can insert text into a Google Docs document using this code:
function appendToDocument() {
let offset = 12;
let updateObject = {
documentId: 'xxxxxxx',
resource: {
requests: [{
"insertText": {
"text": "John Doe",
"location": {
"index": offset,
},
},
}],
},
};
gapi.client.docs.documents.batchUpdate(updateObject).then(function(response) {
appendPre('response = ' + JSON.stringify(response));
}, function(response) {
appendPre('Error: ' + response.result.error.message);
});
}
My next step is to create an entire, complex document using the api. I am stunned by what appears to be the fact that I need to maintain locations into the documents, like this
new Location().setIndex(25)
I am informing myself of that opinion by reading this https://developers.google.com/docs/api/how-tos/move-text
The document I am trying to create is very dynamic and very complex, and handing the coding challenge to keeping track of index values to the api user, rather than the api designer, seems odd.
Is there an approach, or a higher level api, that allows me construct a document without this kind of house keeping?
Unfortunately, the short answer is no, there's no API that lets you bypass the index-tracking required of the base Google Docs API - at least when it comes to building tables.
I recently had to tackle this issue myself - a combination of template updating and document construction - and I basically ended up writing an intermediate API with helper functions to search for and insert by character indices.
For example, one trick I've been using for table creation is to first create a table of a specified size at a given index, and put some text in the first cell. Then I can search the document object for the tableCells element that contains that text, and work back from there to get the table start index.
Another trick is that if you know how many specific kinds of objects (like tables) you have in your document, you can parse through the full document object and keep track of table counts, and stop when you get to the one you want to update/delete (you can use this approach for creating too but the target text approach is easier, I find).
From there with some JSON parsing and trial-and-error, you can figure out the start index of each cell in a table, and write functions to programmatically find and create/replace/delete. If there's an easier way to do all this, I haven't found it. There is one Github repo with a Google Docs API wrapper specifically for tables, and it does appear to be active, although I found it after I wrote everything on my own and I haven't used it.)
Here's a bit of code to get you started:
def get_target_table(doc, target_txt):
""" Given a target string to be matched in the upper left column of a table
of a Google Docs JSON object, return JSON representing that table. """
body = doc["body"]["content"]
for element in body:
el_type = list(element.keys())[-1]
if el_type == "table":
header_txt = get_header_cell_text(element['table']).lower().strip()
if target_txt.lower() in header_txt:
return element
return None
def get_header_cell_text(table):
""" Given a table element in Google Docs API JSON, find the text of
the first cell in the first row, which should be a column header. """
return table['tableRows'][0]\
['tableCells'][0]\
['content'][0]\
['paragraph']['elements'][0]\
['textRun']['content']
Assuming you've already created a table with the target text in it: now, start by pulling the document JSON object from the API, and then use get_target_table() to find the chunk of JSON related to the table.
doc = build("docs", "v1", credentials=creds).documents().get(documentId=doc_id).execute()
table = get_target_table(doc, "my target")
From there you'll see the nested tableRows and tableCells objects, and the content inside each cell has a startIndex. Construct a matrix of table cell start indices, and then, for populating them, work backwards from the bottom right cell to the upper left, to avoid displacing your stored indices (as suggested in the docs and in one of the comments).
It's definitely a bit of a slog. And styling table cells is a whole 'nother beast, which is a dizzying maze of JSON options. The interactive JSON constructor tool on the Docs API site is useful to get the syntax write.
Hope this helps, good luck!
The answer I arrived at: You can create Docs without using their JSON schema.
https://developers.google.com/drive/api/v3/manage-uploads#node.js_1
So, create the document in your format of choice (HTML, DocX, MD (you'd use pandoc to convert MD to another format)), and then upload that.

Does a ds_map destroy sub-maps?

I am trying to make a lighting system in Game Maker and to structure the system I want to contain all of the lights in a ds_map. I want that ds_map to be held inside a second ds_map that contains all of the lights in the system
lights {
"l-01": {"x":20, "y":40, "radius":15},
...
}
The question I have regards cleaning up the system:
Do I have to iterate through the map destroying all of the sub-maps and then destroy the map, will Game Maker automatically destroy the sub-maps when the light map gets destroyed?
ds_map_destroy(lights); // do the sub maps ("l-01") also get destroyed?
or do I have to do it like this:
var k = ds_map_find_first(lights);
repeat(ds_map_size(lights)){
ds_map_destroy(lights[? k]);
k = ds_map_find_next(lights, k);
}
Following along with the first question, if I delete a key will game maker destroy the sub-map
ds_map_delete(lights, "l-01") // will this destroy the map indexed under "l-01"
You may ask: "Why are you using a ds_map to hold a bunch of ds_maps, why not just create a list of maps?
The answer comes from the second question. If I held the maps in a list, and I need to delete one of the maps the list will resize on removal of the map, therefore offsetting all of the other indexed values
The second reason is that in Game Maker, ds_maps are much quicker than ds_lists
I hope I have made my question clear and that one of you out there has the answer.
If you marked added data structure as list/map (using ds_list_mark_as_map() or ds_list_mark_as_list() or ds_map_add_list() or ds_map_add_map()) then it will be deleted automatically. Otherwise you need delete it yourself.
From documentation about ds_list_mark_as_list():
NOTE: Once a ds_list has had a value within it flagged as another list or map, destroying the list will also destroy the marked lists and maps too. This means that you do not have to manually go through the list contents and destroy the marked data structures individually before destroying the "parent" list.
and ds_map_add_list():
If a ds_map has a list added in this way, destroying the parent map will also destroy the contained lists and free their memory.
If you have doubts you can check, is data structure exists or not, using ds_exists()

Dynamic Tag Management - Storing

We're in the process of moving to DTM implementation. We have several variables that are being defined on page. I understand I can make these variables available in DTM through data elements. Can I simply set up a data elem
So set data elements
%prop1% = s.prop1
%prop2% = s.prop2
etc
And then under global rules set
s.prop1 = %s.prop1%
s.prop2 = %s.prop2%
etc
for every single evar, sprop, event, product so they populate whenever they are set on a particular page. Good idea or terrible idea? It seems like a pretty bulky approach which raises some alarm bells. Another option would be to write something that pushes everything to the datalayer, but that seems like essentially the same approach with a redundant step when they can be grabbed directly.
Basically I want DTM to access any and all variables that are currently being set with on-page code, and my understanding is that in order to do that they must be stored in a data element first. Does anyone have any insight into this?
I use this spec for setting up data layers: Data Layer Standard
We create data elements for each key that we use from the standard data layer. For example, page name is stored here
digitalData.page.pageInfo.pageName
We create a data element and standardize the names to this format "page.pageInfo.pageName"
Within each variable field, you access it with the %page.pageInfo.pageName% notation. Also, within javascript of rule tags, you can use this:
_satellite.getVar('page.pageInfo.pageName')
It's a bit unwieldy at times but it allows you to separate the development of the data layer and tag manager tags completely.
One thing to note, make sure your data layer is complete and loaded before you call the satellite library.
If you are moving from a legacy s_code implementation to DTM, it is a good best practice to remove all existing "on page" code (including the reference to the s_code file) and create a "data layer" that contains the data from the eVars and props on the page. Then DTM can reference the object on the page and you can create data elements that map to variables.
Here's an example of a data layer:
<script type="text/javascript">
DDO = {} // Data Layer Object Created
DDO.specVersion = "1.0";
DDO.pageData = {
"pageName":"My Page Name",
"pageSiteSection":"Home",
"pageType":"Section Front",
"pageHier":"DTM Test|Home|Section Front"
},
DDO.siteData = {
"siteCountry":"us",
"siteRegion":"unknown",
"siteLanguage":"en",
"siteFormat":"Desktop"
}
</script>
The next step would be to create data elements that directly reference the values in the object. For example, if I wanted to create a data element that mapped to the page name element in my data layer I would do the following in DTM:
Create a new data element called "pageName"
Select the type as "JS Object"
In the path field I will reference the path to the page name in my data layer example above - DDO.pageData.pageName
Save the data element
Now this data element can be referenced in any variable field within any rule by simply typing a '%'. DTM will find any existing data elements and you can select them.
I also wrote about a simple script you can add to your implementation to help with your data layer validation.Validate your DTM Data Layer with this simple script
Hope this helps.

Gmaps4Rails v2: how to remove marker using a custom attribute?

I must be missing something obvious - I am basically trying to do in v2, what I think is outlined in [this SO question], except that doesn't work in v2. Specifically, Gmaps.maps is no longer defined.
I've gotten as far as figuring that if I store the marker data array that's returned when I call addMarkers in the buildMaps callback, I can use the elements of that array to delete a marker.
If I'm storing a custom attribute in the JSON that I sent to addMarkers, I can then hold on to that JSON array as well, and query for that attribute, find the index, and then hide the marker with that index in the marker data array - here's what I mean, in pseudo-code:
json_array=generate_json();
handler.build_map({}, function() { window.marker_data=handler.addMarkers(); });
indexes=find_in_json(json_array, {"type":"hotel"});
marker_data[i].hide() for i in indexes;
But this means I have the same conceptual data in two places - is there a better way to do this that avoids managing the "model" of the marker in two separate arrays?
I understand your concern.
What I do is to merge data in this case, check http://apneadiving.github.io/ there is an example (check sidebar section)
var markers = handler.addMarkers(json_array);
_.each(json_array, function(json, index){
json.marker = markers[index];
});

Is it possible to replace one marker using gmaps4rails?

Using Gmaps.map.replaceMarkers() I can replace entire marker set. But in my application I will use more than 3000 markers.
How to replace one marker or subset of markers?
This is definitely possible but because there is no rule, there is custom code to write.
Here is how I proceed generally:
I add some custom data in the marker's json, the id of the active record's object generally
I sort my markers client side in javascript variables, say you have the ids of markers you want to remove in a js array called toClear
I remove useless markers:
Coffeescript:
for marker in Gmaps.map.markers
if marker.id in toClear
Gmaps.map.clearMarker marker
I add the new one Gmaps.addMarkers new_markers_array

Resources