The client-side code remains virtually unchanged from the version of the code between Javascript and PHP. Exactly one line needs changing. The route is now mapped to a function in the Python code.

Writing the server using Flask

In contrast with monolithic back-end libraries, Flask lets developers make many decisions, such as choosing the structure of the system and the extensions to use.

We create an instance of the Flask object and define the routes. We serve the route route ('/') by responding with the function index.

from geom import Point, Segment, Line, intersect
from flask import Flask, render_template, request, jsonify, abort

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

The app is launched using app.run().

if __name__=="__main__":
    app.run(host='0.0.0.0')

We also respond at the URL /calculate_intersection by running the function with the same name. Both the request and the response are JSON strings. We construct two segments, compute their intersection, if any, and respond with the point of intersection.

# Both request and response are JSON.
@app.route('/calculate_intersection', methods=['POST'])
def calculate_intersection():
    if not request.json:
        abort(400)
    seg1 = Segment(request.json['seg1'])
    seg2 = Segment(request.json['seg2'])
    result = intersect(seg1, seg2)
    if result is None:
        return jsonify({'intersection': False})
    else:
        return jsonify({'intersection': True, 'x': result.x, 'y': result.y})

Geometry

Here also we do not aim to build a comprehensive geometry library, but only those functions needed, starting with the humble determinant, the cornerstone of geometric calculations.

def determinant(a, b, c, d):
    return a * d - b * c

A Point object can take either one or two arguments. If one, we expect a dict, and if two, we expect the two coordinates.

class Point(object):
    def __init__(self, *args):
        if len(args) == 1 and type(args[0]) is dict:
            self.x, self.y = float(args[0]['x']), float(args[0]['y'])
        elif len(args) == 2:
            self.x, self.y = args[0], args[1]
        else:
            raise TypeError('Unable to construct Point.')

A Segment is initialized by a dict, as received in the JSON message, and a Line is initialized by a segment.

class Segment(object):
    def __init__(self, msg):
        self.src = Point(msg['src'])
        self.tgt = Point(msg['tgt'])

class Line(object):
    def __init__(self, seg):
        self.a = + determinant(seg.src.y,       1.0, seg.tgt.y,       1.0)
        self.b = - determinant(seg.src.x,       1.0, seg.tgt.x,       1.0)
        self.c = + determinant(seg.src.x, seg.src.y, seg.tgt.x, seg.tgt.y)

To compute the intersection, we first determine whether the endpoints of each segment lie on opposite sides of the other.

class Side(object):
    def __init__(self, v):
        self.v = +1 if v > 0 else (0 if v == 0 else -1)

def oriented_side(line, point):
    return Side(line.a * point.x + line.b * point.y + line.c)

def straddle(line, segment):
    src_side = oriented_side(line, segment.src)
    tgt_side = oriented_side(line, segment.tgt)
    s1 = src_side.v >= 0 and tgt_side.v <  0
    s2 = src_side.v <  0 and tgt_side.v >= 0
    return s1 or s2

If both do, we find the intersection point.

def intersect(seg1, seg2):
    l1 = Line(seg1)
    l2 = Line(seg2)
    if straddle(l1, seg2) and straddle(l2, seg1):
        denom = determinant(l1.a, l1.b, l2.a, l2.b);
        if denom != 0:
            detx = + determinant(l1.b, l1.c, l2.b, l2.c);
            dety = - determinant(l1.a, l1.c, l2.a, l2.c);
            return Point(detx/denom, dety/denom);
    return None

The source code is here.