Add template popup to GeoJSON component with Python Dash-Leaflet - python

I am a very beginner in programmation and Python. I have a map application built with dash-leaflet with several (~10) GeoJSON files included by dl.GeoJSON component. I would like to show a popup with all the properties of each file. Before dl.GeoJSON was implemented, i used to create my layers by reading my geojson and defining popup like this :
def compute_geojson(gjson):
geojson = json.load(open(gjson["path"],encoding='utf8'))
if 'Polygon' in geojson["features"][0]["geometry"]["type"]:
data = [
dl.Polygon(
positions=get_geom(feat),
children=[
dl.Popup([html.P(k + " : " + str(v)) for k,v in feat["properties"].items()],maxHeight=300),
],
color=get_color(gjson,feat), weight=0.2, fillOpacity=gjson["opacity"], stroke=True
) for feat in geojson['features']
]
...
I would like to do this for all my geojson (which have different structures) with the component dl.GeoJSON because it should render faster than my method. Is it possible ? I tried some javascript with onEachFeature but didn't succeed.
Thanks

The simplest solution would be to add a feature named popup with the desired popup content, as the GeoJSON component will render it as a popup automatically,
import dash_leaflet as dl
import dash_leaflet.express as dlx
data = dlx.dicts_to_geojson([dict(lat=-37.8, lon=175.6, popup="I am a popup")])
geojson = dl.GeoJSON(data=data)
If you need more customization options and/or prefer not to add properties (e.g. for performance reasons), you would need to implement a custom onEachFeature function. If you create a .js file in your assets folder with content like,
window.someNamespace = Object.assign({}, window.someNamespace, {
someSubNamespace: {
bindPopup: function(feature, layer) {
const props = feature.properties;
delete props.cluster;
layer.bindPopup(JSON.stringify(props))
}
}
});
you can bind the function like this,
import dash_leaflet as dl
from dash_extensions.javascript import Namespace
ns = Namespace("someNamespace", "someSubNamespace")
geojson = dl.GeoJSON(data=data, options=dict(onEachFeature=ns("bindPopup")))
In the above code examples i am using dash-leaflet==0.1.10 and dash-extensions==0.0.33.

Related

How to propagate mlpipeline-metrics from custom Python function TFX component?

Note: this is a copy of a GitHub issue I reported.
It is re-posted in hope to get more attention, I will update any solutions on either site.
Question
I want to export mlpipeline-metrics from my custom Python function TFX component so that it is displayed in the KubeFlow UI.
This is a minimal example of what I am trying to do:
import json
from tfx.dsl.component.experimental.annotations import OutputArtifact
from tfx.dsl.component.experimental.decorators import component
from tfx.types.standard_artifacts import Artifact
class Metric(Artifact):
TYPE_NAME = 'Metric'
#component
def ShowMetric(MLPipeline_Metrics: OutputArtifact[Metric]):
rmse_eval = 333.33
metrics = {
'metrics':[
{
'name': 'RMSE-validation',
'numberValue': rmse_eval,
'format': 'RAW'
}
]
}
path = '/tmp/mlpipeline-metrics.json'
with open(path, 'w') as _file:
json.dump(metrics, _file)
MLPipeline_Metrics.uri = path
In the KubeFlow UI, the "Run output" tab says "No metrics found for this run." However, the output artefact shows up in the ML MetaData (see screenshot). Any help on how to accomplish this would be greatly appreciated. Thanks!

How to set colour of background plane to white using Bokeh in Python

I am looking for the matplotlib set plane color equivalent in Bokeh. I generated the image by wrapping TS code into Python. I received the code from the example section of Bokeh API Doc. All options are currently in their default mode. I tried playing around with various options such as: Background, showGrayColor, showShadow etc but nothing seems to get rid of the shadow. Any help would be appreciated!
The image I have looks like this:
I want the background to be white instead of the shadow currently visible. Anyone know how to do this?
This is the code I used:
TS_CODE1 = """
// This custom model wraps one part of the third-party vis.js library:
//
// http://visjs.org/index.html
//
// Making it easy to hook up python data analytics tools (NumPy, SciPy,
// Pandas, etc.) to web presentations using the Bokeh server.
import {LayoutDOM, LayoutDOMView} from "models/layouts/layout_dom"
import {ColumnDataSource} from "models/sources/column_data_source"
import {LayoutItem} from "core/layout"
import * as p from "core/properties"
declare namespace vis {
class Graph3d {
constructor(el: HTMLElement, data: object, OPTIONS: object)
setData(data: vis.DataSet): void
}
class DataSet {
add(data: unknown): void
}
}
// This defines some default options for the Graph3d feature of vis.js
// See: http://visjs.org/graph3d_examples.html for more details.
const OPTIONS = {
width: '500px',
height: '500px',
style: 'surface',
showPerspective: false,
showGrid: true,
keepAspectRatio: true,
verticalRatio: 1.0,
legendLabel: 'stuff',
cameraPosition: {
horizontal:2,
vertical: 0.5,
distance: 1.8,
},
}
// To create custom model extensions that will render on to the HTML canvas
// or into the DOM, we must create a View subclass for the model.
//
// In this case we will subclass from the existing BokehJS ``LayoutDOMView``
export class Surface3d1View extends LayoutDOMView {
model: Surface3d1
private _graph: vis.Graph3d
initialize(): void {
super.initialize()
const url = "https://cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.js"
const script = document.createElement("script")
script.onload = () => this._init()
script.async = false
script.src = url
document.head.appendChild(script)
}
private _init(): void {
// Create a new Graph3s using the vis.js API. This assumes the vis.js has
// already been loaded (e.g. in a custom app template). In the future Bokeh
// models will be able to specify and load external scripts automatically.
//
// BokehJS Views create <div> elements by default, accessible as this.el.
// Many Bokeh views ignore this default <div>, and instead do things like
// draw to the HTML canvas. In this case though, we use the <div> to attach
// a Graph3d to the DOM.
this._graph = new vis.Graph3d(this.el, this.get_data(), OPTIONS)
// Set a listener so that when the Bokeh data source has a change
// event, we can process the new data
this.connect(this.model.data_source.change, () => {
this._graph.setData(this.get_data())
})
}
// This is the callback executed when the Bokeh data has an change. Its basic
// function is to adapt the Bokeh data source to the vis.js DataSet format.
get_data(): vis.DataSet {
const data = new vis.DataSet()
const source = this.model.data_source
for (let i = 0; i < source.get_length()!; i++) {
data.add({
x: source.data[this.model.x][i],
y: source.data[this.model.y][i],
z: source.data[this.model.z][i],
})
}
return data
}
get child_models(): LayoutDOM[] {
return []
}
_update_layout(): void {
this.layout = new LayoutItem()
this.layout.set_sizing(this.box_sizing())
}
}
// We must also create a corresponding JavaScript BokehJS model subclass to
// correspond to the python Bokeh model subclass. In this case, since we want
// an element that can position itself in the DOM according to a Bokeh layout,
// we subclass from ``LayoutDOM``
export namespace Surface3d1 {
export type Attrs = p.AttrsOf<Props>
export type Props = LayoutDOM.Props & {
x: p.Property<string>
y: p.Property<string>
z: p.Property<string>
data_source: p.Property<ColumnDataSource>
}
}
export interface Surface3d1 extends Surface3d1.Attrs {}
export class Surface3d1 extends LayoutDOM {
properties: Surface3d1.Props
__view_type__: Surface3d1View
constructor(attrs?: Partial<Surface3d1.Attrs>) {
super(attrs)
}
// The ``__name__`` class attribute should generally match exactly the name
// of the corresponding Python class. Note that if using TypeScript, this
// will be automatically filled in during compilation, so except in some
// special cases, this shouldn't be generally included manually, to avoid
// typos, which would prohibit serialization/deserialization of this model.
static __name__ = "Surface3d1"
static init_Surface3d1() {
// This is usually boilerplate. In some cases there may not be a view.
this.prototype.default_view = Surface3d1View
// The #define block adds corresponding "properties" to the JS model. These
// should basically line up 1-1 with the Python model class. Most property
// types have counterparts, e.g. ``bokeh.core.properties.String`` will be
// ``p.String`` in the JS implementatin. Where the JS type system is not yet
// as rich, you can use ``p.Any`` as a "wildcard" property type.
this.define<Surface3d1.Props>({
x: [ p.String ],
y: [ p.String ],
z: [ p.String ],
data_source: [ p.Instance ],
})
}
}
"""
# This custom extension model will have a DOM view that should layout-able in
# Bokeh layouts, so use ``LayoutDOM`` as the base class. If you wanted to create
# a custom tool, you could inherit from ``Tool``, or from ``Glyph`` if you
# wanted to create a custom glyph, etc.
class Surface3d1(LayoutDOM):
# The special class attribute ``__implementation__`` should contain a string
# of JavaScript code that implements the browser side of the extension model.
__implementation__ = TypeScript(TS_CODE1)
# Below are all the "properties" for this model. Bokeh properties are
# class attributes that define the fields (and their types) that can be
# communicated automatically between Python and the browser. Properties
# also support type validation. More information about properties in
# can be found here:
#
# https://docs.bokeh.org/en/latest/docs/reference/core/properties.html#bokeh-core-properties
# This is a Bokeh ColumnDataSource that can be updated in the Bokeh
# server by Python code
data_source = Instance(ColumnDataSource)
# The vis.js library that we are wrapping expects data for x, y, and z.
# The data will actually be stored in the ColumnDataSource, but these
# properties let us specify the *name* of the column that should be
# used for each field.
x = String
y = String
z = String
surface1 = Surface3d1(x="x", y="y", z="z", data_source=source, width=600, height=600)
indent preformatted text by 4 spaces

How to use variables from backend in .scss files

I have a site built with Django, Python and Wagtail.
What I want is to be able to add some styles in the backend and then use it in my frontent's .scss files.
For example I want to be able to set a primary color to #fff via backend and then use it in my .scss file as:
$g-color-primary: primary_color_from_backend;
$g-font-size-default: primary_font_size_from_backend;
I do not have any idea how I can do that and if it's possible at all?
Thanks for the help.
Unfortunately, it is not possible. You can instead define different classes in the CSS file, then use them in your HTML template dependent on the Django template variables there.
This would require to write out the sass color variables content (with scss syntax) in a physical .scss file, which depends on your development environment. And then import it into other .scss file to get compiled and output through a frontend build process tool like Gulp, Webpack.
For example, Webpack's sass-loader plugin provides option to prepend sass code at Frontend build/compile time.
https://github.com/webpack-contrib/sass-loader
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
'style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
prependData: '$env: ' + process.env.NODE_ENV + ';',
},
},
],
},
],
},
};

json to geoDjango model

I am building a database using Django, geodjango and postgresql of field data. The data includes lats and lons. One of the tasks I have is to ingest data that has already been collected. I would like to use .json file to define the metadata and write some code to batch process some json files.
What I have so far is, a model:
class deployment(models.Model):
'''
#brief This is the abstract deployment class.
'''
startPosition=models.PointField()
startTimeStamp=models.DateTimeField()
endTimeStamp=models.DateTimeField()
missionAim=models.TextField()
minDepth=models.FloatField() # IT seems there is no double in Django
maxDepth=models.FloatField()
class auvDeployment(deployment):
'''
#brief AUV meta data
'''
#==================================================#
# StartPosition : <point>
# distanceCovered : <double>
# startTimeStamp : <dateTime>
# endTimeStamp : <dateTime>
# transectShape : <>
# missionAim : <Text>
# minDepth : <double>
# maxDepth : <double>
#--------------------------------------------------#
# Maybe need to add unique AUV fields here later when
# we have more deployments
#==================================================#
transectShape=models.PolygonField()
distanceCovered=models.FloatField()
And I function I want to use to ingest the data
#staticmethod
def importDeploymentFromFile(file):
'''
#brief This function reads in a metadta file that includes campaign information. Destinction between deployment types is made on the fine name. <type><deployment>.<supported text> auvdeployment.json
#param file The file that holds the metata data. formats include .json todo:-> .xml .yaml
'''
catamiWebPortal.logging.info("Importing metadata from " + file)
fileName, fileExtension = os.path.splitext(file)
if fileExtension == '.json':
if os.path.basename(fileName.upper()) == 'AUVDEPLOYMENT':
catamiWebPortal.logging.info("Found valid deployment file")
data = json.load(open(file))
Model = auvDeployment(**data)
Model.save()
And the file I am trying to read in this
{
"id":1,
"startTimeStamp":"2011-09-09 13:20:00",
"endTimeStamp":"2011-10-19 14:23:54",
"missionAim":"for fun times, call luke",
"minDepth":10.0,
"maxDepth":20.0,
"startPosition":{{"type": "PointField", "coordinates": [ 5.000000, 23.000000 ] }},
"distanceCovered":20.0
}
The error that I am getting is this
TypeError: cannot set auvDeployment GeometryProxy with value of type: <type 'dict'>
If I remove the geo types from the model and file. It will read the file and populate the database table.
I would appreciate any advice one how I am parse the datafile with the geotypes.
Thanks
Okay the solution is as follows. The file format is not the geoJSON file format, it's the geos format. The .json file should be as follows.
{
"id": 1,
"startTimeStamp": "2011-10-19 10:23:54",
"endTimeStamp":"2011-10-19 14:23:54",
"missionAim": "for fun times, call luke",
"minDepth":10.0,
"maxDepth":20.0,
"startPosition":"POINT(-23.15 113.12)",
"distanceCovered":20,
"transectShape":"POLYGON((-23.15 113.12, -23.53 113.34, -23.67 112.9, -23.25 112.82, -23.15 113.12))"
}
Not the StartPosition syntax has changed.
A quick fix would be to use the GEOs API in geoDjango to change the startPosition field from geoJson format to a GEOSGeometry object before you save the model. This should allow it to pass validation.
Include the GEOSGeometry function from Django with:
from django.contrib.gis.geos import GEOSGeometry
...
Model = auvDeployment(**data)
Model.startPosition = GEOSGeometry(str(Model.startPosition))
Model.save()
The GEOS API cant construct objects from a GeoJSON format, as long as you make it a string first. As it stands, you are loading it as a dictionary type instead of a string.
I suggest you use the default command for loading fixtures: loaddata
python manage.py loaddata path/to/myfixture.json ...
The structure of your json would have to be slighty adjusted, but you could make a simple dumpdata to see how the structure should look like.

Jinja-like for Pdf in Python

I am looking for the best accurate tool for PDF in Python that works like Jinja does for HTML.
What are your suggestions?
As answered by jbochi, ReportLab is the foundation for almost all Python projects that generate PDF.
But for your needs you might want to check out Pisa / xhtml2pdf. You would generate your HTML with a Jinja template and then use Pisa to convert the HTML to PDF. Pisa is built on top of ReportLab.
Edit: another option I'd forgotten about is wkhtmltopdf
Have a look at ReportLab Toolkit.
You can use templates only with the commercial version, though.
There's now a new kid on the block called WeasyPrint.
I had exactly the same requirement as the OP. Unfortunately WeasyPrint wasn't a viable solution, because I needed very exact positioning and barcode support. After a few days of work I finished a reportlab XML wrapper with Jinja2 support.
The code can be found on GitHub
including an example XML wich generates the following PDF.
What about python/jinja to rst/html and html/rst to pdf using either rst2pdf or pandoc.
Both of these have worked well for me but. like plaes, I may try Weasyprint in the future.
What more accurate tool for PDF in Python that works like Jinja than Jinja itself?
You just have to make sure that the Jinja block, variable, and comment identification strings do not conflict with the LaTeX commands. Once you change the Jinja environment to mimic the LaTeX environment you're ready to go!
Here's a snippet that works out of the box:
Python Source: ./create_pdf.py
import os, jinja2
from jinja2 import Template
latex_jinja_env = jinja2.Environment(
block_start_string = '\BLOCK{',
block_end_string = '}',
variable_start_string = '\VAR{',
variable_end_string = '}',
comment_start_string = '\#{',
comment_end_string = '}',
line_statement_prefix = '%%',
line_comment_prefix = '%#',
trim_blocks = True,
autoescape = False,
loader = jinja2.FileSystemLoader(os.path.abspath('./latex/'))
)
template = latex_jinja_env.get_template('latex_template.tex')
# populate a dictionary with the variables of interest
template_vars = {}
template_vars['section_1'] = 'The Section 1 Title'
template_vars['section_2'] = 'The Section 2 Title'
# create a file and save the latex
output_file = open('./generated_latex.tex', 'w')
# pass the dictionary with variable names to the renderer
output_file.write( template.render( template_vars ) )
output_file.close()
Latex Template: ./latex/latex_template.tex
\documentclass{article}
\begin{document}
\section{Example}
An example document using \LaTeX, Python, and Jinja.
% This is a regular LaTeX comment
\section{\VAR{section_1}}
\begin{itemize}
\BLOCK{ for x in range(0,3) }
\item Counting: \VAR{x}
\BLOCK{ endfor }
\end{itemize}
\#{This is a long-form Jinja comment}
\BLOCK{ if subsection_1_1 }
\subsection{ The subsection }
This appears only if subsection_1_1 variable is passed to renderer.
\BLOCK{ endif }
%# This is a short-form Jinja comment
\section{\VAR{section_2}}
\begin{itemize}
%% for x in range(0,3)
\item Counting: \VAR{x}
%% endfor
\end{itemize}
\end{document}
Now simply call: $> python ./create_pdf.py
Resulting Latex Source: ./generated_latex.tex
\documentclass{article}
\begin{document}
\section{Example}
An example document using \LaTeX, Python, and Jinja.
% This is a regular LaTeX comment
\section{The Section 1 Title}
\begin{itemize}
\item Counting: 0
\item Counting: 1
\item Counting: 2
\end{itemize}
\section{The Section 2 Title}
\begin{itemize}
\item Counting: 0
\item Counting: 1
\item Counting: 2
\end{itemize}
\end{document}
Generated Pdf:
References:
About Django's syntax conflict with LaTex
About latex templates
About passing a dict to render_template
If you want to use existing PDF as template, without altering original document, you can use Dhek template editor, which allows to define area (bounds, name, type) in a separate template file.
Template is saved in JSON format so that it can be parsed in Python, to fill areas over PDF and generate the final document (e.g. with values from Web form).
See documentation at https://github.com/applicius/dhek .
[EDIT]
Initial answer was from the author of dhek.
I have used this tool and this is great if your form has not been generated in the usual way (it even works on PDF done from images).
After you downloaded, unzipped, and run DHEK (no install needed, it is portable), you can select areas and given them a name:
You can then save the "mapping" to JSON so you can get the positions and dimensions of the areas:
{
"pages": [
{
"areas": [
{
"name": "FirstName",
"x": 198.48648648648648,
"type": "text",
"y": 151.22779922779924,
"height": 15.75289575289574,
"width": 181.15830115830119
},
{
"name": "LastName",
"x": 195.33590733590734,
"type": "text",
"y": 176.43243243243245,
"height": 18.115830115830107,
"width": 185.0965250965251
}
]
}
],
"format": "dhek-1.0.13"
}
You can then use these positions with reportlab to create a PDF that contains the text:
from reportlab.pdfgen.canvas import Canvas
def write_text(
canvas: Canvas, txt: str, x: float, y: float, height: float, in_middle: bool = True
) -> None:
"""Write text in a form (in middle of height)"""
if canvas.bottomup:
y = canvas._pagesize[1] - y
canvas.drawString(x, y + height / 2, txt)
def create_overlay(overlay_path: str):
"""
Create the data that will be overlayed on top
of the form that we want to fill
"""
c = Canvas(overlay_path, bottomup=0) # DHEK has (0,0) at top-left
write_text(c, "Mike", 198.48648648648648, 151.22779922779924, 15.75289575289574)
write_text(c, "Jagger", 195.33590733590734, 176.43243243243245, 18.115830115830107)
c.save()
create_overlay("form_overlay.pdf")
You can then use any tool / pdf library (e.g. pdfrw) to merge the two in a single page:
import pdfrw
def merge_pdfs(form_pdf, overlay_pdf, output):
"""
Merge the specified fillable form PDF with the
overlay PDF and save the output
"""
form = pdfrw.PdfReader(form_pdf)
olay = pdfrw.PdfReader(overlay_pdf)
for form_page, overlay_page in zip(form.pages, olay.pages):
merge_obj = pdfrw.PageMerge()
overlay = merge_obj.add(overlay_page)[0]
pdfrw.PageMerge(form_page).add(overlay).render()
writer = pdfrw.PdfWriter()
writer.write(output, form)
merge_pdfs("form.pdf", "form_overlay.pdf", "form_filled.pdf")
(last part of code to create Overlay and Merge is coming from the Excellent blog "Mouse vs Python": https://www.blog.pythonlibrary.org/2018/05/22/filling-pdf-forms-with-python/)
... There is also the library pdfjinja that is for this purpose: https://github.com/rammie/pdfjinja
It is using annotations to create template values.
In my use case, I didn't have a PDF with proper form fields so the solution suggested by cchantep was more suitable.

Categories

Resources