What is the best practice for Cloud Functions for this scenario, I have two possible scenarios (or maybe you can see another)
I’d like to make an API that receives/upload a file (.tif with geodata) and extract properties from the file and add the URL returned and the extracted properties to a Parent Parse Object.
Potential Solution 1: Upload the tiff file directly from the Client App (without Cloud Code) and get the extents of the GeoTiff in FileTrigger’s afterSaveFile
Parent Class:
- pointer to uploadedTif: String(URL)
- tif_properties: Object (JSON data)
An issue with this, is that User Upload other types of files, so I assume I can check if (file extension == ‘tif’) prior to extracting the GeoTiff extents.
I am not sure where to put this in Cloud Code, since I cannot call the existing hello
function which is in cloud/functions.js:
Parse.Cloud.define('hello', (req) => {
return "Hello from Simple Cloud Code :)";
});
Making an API call to hello:
curl "http://localhost:1337/1/functions/hello" \ // also tried http://localhost:1337/1/hello & http://localhost:1337/hello as unauthorized error.
-H 'X-Parse-Application-Id: XYZ' \
-H 'X-Parse-Rest-Api-Key: ABC' \
-H 'Content-Type: application/json'
I get back: {"error":"unauthorized"}
Potential Solution 2: POST a JSON object (with the fileData as one of the JSON properties)
- Users need to Upload a File (GeoTiff file).
- GeoTiff files have special properties called extents within it (that are extracted with an npm called
geotiff-extents
and other packages), - When the File finishes uploading on Cloud Code, the library extracts these properties (code below)
- The URL of the recently uploaded .tif file is then a Pointer (1 to 1) to a Parent Object (a class object on Parse)
- The extracted properties of the tiff file are then added as a property object of the Parent class obj
The issue I am facing is that I cannot seem to Post to my cloud function a JSON and one of the properties is a File data. I’m fairly confident that the issue stems from the header Content-Type
If I set Content-Type
as application/json
the req.body
only shows the properties without the fileData
If I set Content-Type
as multipart/form-data
the req.body is {}
Cloud Code:
app.js
var express = require('express'),
fs = require('fs'),
geotiff = require('geotiff'),
epsg = require('epsg-to-proj'),
extents = require('geotiff-extents');
var app = express();
app.use(express.json({limit: '20mb'}))
app.use(express.urlencoded({limit: '20mb', extended: true }))
app.get('/hello-advanced', (req, res) => {
res.send("Hello from Advanced Cloud Code");
});
app.post('/uploadExpandTiff', (req, res) => {
var result = {};
console.info("-- Start --");
console.info("req.body " + JSON.stringify(req.body));
if (req.body.file) {
// 1- Get raw file binary data
var fileData = fs.createReadStream(req.body.file);
result.fileAdded = true;
// 2- Parse tif & get GeoBounds
fs.readFile(req.body.file, function (err, data) {
if (err) throw err;
var dataArray = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
var im = geotiff.parse(dataArray).getImage();
var fd = im.getFileDirectory();
var gk = im.getGeoKeys();
// extract .tif Exif info & generate coordinate bounds
var boundsCoords = extents({
tiePoint: fd.ModelTiepoint,
pixelScale: fd.ModelPixelScale,
width: fd.ImageWidth,
height: fd.ImageLength,
proj: require('proj4'),
from: epsg[gk.ProjectedCSTypeGeoKey || gk.GeographicTypeGeoKey],
to: epsg[4326]
});
result.tifBounds = true;
console.info(".tif Properties:\n" + JSON.stringify(boundsCoords));
// return results
res.send(result);
// 3- TODO Upload .tif to parse
// 4- TODO Update Parent classname to have a Pointer to geotiff file & add properties to Parent classname with the extents properties
});
} else {
result.fileAdded = false;
result.error = "Error: No File data Found";
console.info("Error: No req.body.file Found " + JSON.stringify(req.body));
res.send(result);
}
});
So I’m blocked on both paths. Using afterSaveFile
looks more promising & a cleaner solution.